var assert = require('assert') var poolModule = require('..') module.exports = { 'expands to max limit': function (beforeExit) { var createCount = 0 var destroyCount = 0 var borrowCount = 0 var factory = { name: 'test1', create: function (callback) { callback(null, { count: ++createCount }) }, destroy: function (client) { destroyCount++ }, max: 2, idleTimeoutMillis: 100 } var pool = poolModule.Pool(factory) for (var i = 0; i < 10; i++) { var full = !pool.acquire(function () { return function (err, obj) { assert.ifError(err) assert.equal(typeof obj.count, 'number') setTimeout(function () { borrowCount++ pool.release(obj) }, 100) } }()) assert.ok((i < 1) ^ full) } beforeExit(function () { assert.equal(0, factory.min) assert.equal(2, createCount) assert.equal(2, destroyCount) assert.equal(10, borrowCount) }) }, 'respects min limit': function (beforeExit) { var createCount = 0 var destroyCount = 0 var pool = poolModule.Pool({ name: 'test-min', create: function (callback) { callback(null, { count: ++createCount }) }, destroy: function (client) { destroyCount++ }, min: 1, max: 2, idleTimeoutMillis: 100 }) pool.drain(function () { pool.destroyAllNow() }) beforeExit(function () { assert.equal(0, pool.availableObjectsCount()) assert.equal(1, createCount) assert.equal(1, destroyCount) }) }, 'min and max limit defaults': function (beforeExit) { var factory = { name: 'test-limit-defaults', create: function (callback) { callback(null, {}) }, destroy: function (client) {}, idleTimeoutMillis: 100 } var pool = poolModule.Pool(factory) beforeExit(function () { assert.equal(1, pool.getMaxPoolSize()) assert.equal(0, pool.getMinPoolSize()) }) }, 'malformed min and max limits are ignored': function (beforeExit) { var factory = { name: 'test-limit-defaults2', create: function (callback) { callback(null, {}) }, destroy: function (client) {}, idleTimeoutMillis: 100, min: 'asf', max: [] } var pool = poolModule.Pool(factory) beforeExit(function () { assert.equal(1, pool.getMaxPoolSize()) assert.equal(0, pool.getMinPoolSize()) }) }, 'min greater than max sets to max minus one': function (beforeExit) { var factory = { name: 'test-limit-defaults3', create: function (callback) { callback(null, {}) }, destroy: function (client) {}, idleTimeoutMillis: 100, min: 5, max: 3 } var pool = poolModule.Pool(factory) pool.drain(function () { pool.destroyAllNow() }) beforeExit(function () { assert.equal(3, pool.getMaxPoolSize()) assert.equal(2, pool.getMinPoolSize()) }) }, 'supports priority on borrow': function (beforeExit) { var borrowTimeLow = 0 var borrowTimeHigh = 0 var borrowCount = 0 var i var pool = poolModule.Pool({ name: 'test2', create: function (callback) { callback() }, destroy: function (client) {}, max: 1, idleTimeoutMillis: 100, priorityRange: 2 }) for (i = 0; i < 10; i++) { pool.acquire((function (err, obj) { return function () { setTimeout(function () { assert.ifError(err) var t = new Date().getTime() if (t > borrowTimeLow) { borrowTimeLow = t } borrowCount++ pool.release(obj) }, 50) } }()), 1) } for (i = 0; i < 10; i++) { pool.acquire((function (obj) { return function () { setTimeout(function () { var t = new Date().getTime() if (t > borrowTimeHigh) { borrowTimeHigh = t } borrowCount++ pool.release(obj) }, 50) } }()), 0) } beforeExit(function () { assert.equal(20, borrowCount) assert.equal(true, borrowTimeLow > borrowTimeHigh) }) }, 'removes correct object on reap': function (beforeExit) { var destroyed = [] var clientCount = 0 var pool = poolModule.Pool({ name: 'test3', create: function (callback) { callback(null, { id: ++clientCount }) }, destroy: function (client) { destroyed.push(client.id) }, max: 2, idleTimeoutMillis: 100 }) pool.acquire(function (err, client) { assert.ifError(err) assert.equal(typeof client.id, 'number') // should be removed second setTimeout(function () { pool.release(client) }, 5) }) pool.acquire(function (err, client) { assert.ifError(err) assert.equal(typeof client.id, 'number') // should be removed first pool.release(client) }) setTimeout(function () {}, 102) beforeExit(function () { assert.equal(2, destroyed[0]) assert.equal(1, destroyed[1]) }) }, 'tests drain': function (beforeExit) { var created = 0 var destroyed = 0 var count = 5 var acquired = 0 var pool = poolModule.Pool({ name: 'test4', create: function (callback) { callback(null, {id: ++created}) }, destroy: function (client) { destroyed += 1 }, max: 2, idletimeoutMillis: 300000 }) for (var i = 0; i < count; i++) { pool.acquire(function (err, client) { assert.ifError(err) acquired += 1 assert.equal(typeof client.id, 'number') setTimeout(function () { pool.release(client) }, 250) }) } assert.notEqual(count, acquired) pool.drain(function () { assert.equal(count, acquired) // short circuit the absurdly long timeouts above. pool.destroyAllNow() beforeExit(function () {}) }) // subsequent calls to acquire should return an error. assert.throws(function () { pool.acquire(function (client) {}) }, Error) }, 'handle creation errors': function (beforeExit) { var created = 0 var pool = poolModule.Pool({ name: 'test6', create: function (callback) { if (created < 5) { callback(new Error('Error occurred.')) } else { callback({ id: created }) } created++ }, destroy: function (client) {}, max: 1, idleTimeoutMillis: 1000 }) // ensure that creation errors do not populate the pool. for (var i = 0; i < 5; i++) { pool.acquire(function (err, client) { assert.ok(err instanceof Error) assert.ok(client === null) }) } var called = false pool.acquire(function (err, client) { assert.ok(err === null) assert.equal(typeof client.id, 'number') called = true }) beforeExit(function () { assert.ok(called) assert.equal(pool.waitingClientsCount(), 0) }) }, 'handle creation errors for delayed creates': function (beforeExit) { var created = 0 var pool = poolModule.Pool({ name: 'test6', create: function (callback) { if (created < 5) { setTimeout(function () { callback(new Error('Error occurred.')) }, 0) } else { setTimeout(function () { callback({ id: created }) }, 0) } created++ }, destroy: function (client) {}, max: 1, idleTimeoutMillis: 1000 }) // ensure that creation errors do not populate the pool. for (var i = 0; i < 5; i++) { pool.acquire(function (err, client) { assert.ok(err instanceof Error) assert.ok(client === null) }) } var called = false pool.acquire(function (err, client) { assert.ok(err === null) assert.equal(typeof client.id, 'number') called = true }) beforeExit(function () { assert.ok(called) assert.equal(pool.waitingClientsCount(), 0) }) }, 'pooled decorator should acquire and release': function (beforeExit) { var assertion_count = 0 var destroyed_count = 0 var pool = poolModule.Pool({ name: 'test1', create: function (callback) { callback({id: Math.floor(Math.random() * 1000)}) }, destroy: function (client) { destroyed_count += 1 }, max: 1, idleTimeoutMillis: 100 }) var pooledFn = pool.pooled(function (client, cb) { assert.equal(typeof client.id, 'number') assert.equal(pool.getPoolSize(), 1) assertion_count += 2 cb() }) assert.equal(pool.getPoolSize(), 0) assertion_count += 1 pooledFn(function (err) { if (err) { throw err } assert.ok(true) assertion_count += 1 }) beforeExit(function () { assert.equal(assertion_count, 4) assert.equal(destroyed_count, 1) }) }, 'pooled decorator should pass arguments and return values': function (beforeExit) { var assertion_count = 0 var pool = poolModule.Pool({ name: 'test1', create: function (callback) { callback({id: Math.floor(Math.random() * 1000)}) }, destroy: function (client) {}, max: 1, idleTimeoutMillis: 100 }) var pooledFn = pool.pooled(function (client, arg1, arg2, cb) { assert.equal(arg1, 'First argument') assert.equal(arg2, 'Second argument') assertion_count += 2 cb(null, 'First return', 'Second return') }) pooledFn('First argument', 'Second argument', function (err, retVal1, retVal2) { if (err) { throw err } assert.equal(retVal1, 'First return') assert.equal(retVal2, 'Second return') assertion_count += 2 }) beforeExit(function () { assert.equal(assertion_count, 4) }) }, 'pooled decorator should allow undefined callback': function (beforeExit) { var assertion_count = 0 var pool = poolModule.Pool({ name: 'test1', create: function (callback) { callback({id: Math.floor(Math.random() * 1000)}) }, destroy: function (client) {}, max: 1, idleTimeoutMillis: 100 }) var pooledFn = pool.pooled(function (client, arg, cb) { assert.equal(arg, 'Arg!') assertion_count += 1 cb() }) pooledFn('Arg!') beforeExit(function () { assert.equal(pool.getPoolSize(), 0) assert.equal(assertion_count, 1) }) }, 'pooled decorator should forward pool errors': function (beforeExit) { var assertion_count = 0 var pool = poolModule.Pool({ name: 'test1', create: function (callback) { callback(new Error('Pool error')) }, destroy: function (client) {}, max: 1, idleTimeoutMillis: 100 }) var pooledFn = pool.pooled(function (cb) { assert.ok(false, "Pooled function shouldn't be called due to a pool error") }) pooledFn(function (err, obj) { assert.equal(err.message, 'Pool error') assertion_count += 1 }) beforeExit(function () { assert.equal(assertion_count, 1) }) }, 'getPoolSize': function (beforeExit) { var assertion_count = 0 var pool = poolModule.Pool({ name: 'test1', create: function (callback) { callback({id: Math.floor(Math.random() * 1000)}) }, destroy: function (client) {}, max: 2, idleTimeoutMillis: 100 }) assert.equal(pool.getPoolSize(), 0) assertion_count += 1 pool.acquire(function (err, obj1) { if (err) { throw err } assert.equal(pool.getPoolSize(), 1) assertion_count += 1 pool.acquire(function (err, obj2) { if (err) { throw err } assert.equal(pool.getPoolSize(), 2) assertion_count += 1 pool.release(obj1) pool.release(obj2) pool.acquire(function (err, obj3) { if (err) { throw err } // should still be 2 assert.equal(pool.getPoolSize(), 2) assertion_count += 1 pool.release(obj3) }) }) }) beforeExit(function () { assert.equal(assertion_count, 4) }) }, 'availableObjectsCount': function (beforeExit) { var assertion_count = 0 var pool = poolModule.Pool({ name: 'test1', create: function (callback) { callback({id: Math.floor(Math.random() * 1000)}) }, destroy: function (client) {}, max: 2, idleTimeoutMillis: 100 }) assert.equal(pool.availableObjectsCount(), 0) assertion_count += 1 pool.acquire(function (err, obj1) { if (err) { throw err } assert.equal(pool.availableObjectsCount(), 0) assertion_count += 1 pool.acquire(function (err, obj2) { if (err) { throw err } assert.equal(pool.availableObjectsCount(), 0) assertion_count += 1 pool.release(obj1) assert.equal(pool.availableObjectsCount(), 1) assertion_count += 1 pool.release(obj2) assert.equal(pool.availableObjectsCount(), 2) assertion_count += 1 pool.acquire(function (err, obj3) { if (err) { throw err } assert.equal(pool.availableObjectsCount(), 1) assertion_count += 1 pool.release(obj3) assert.equal(pool.availableObjectsCount(), 2) assertion_count += 1 }) }) }) beforeExit(function () { assert.equal(assertion_count, 7) }) }, 'logPassesLogLevel': function (beforeExit) { var loglevels = {'verbose': 0, 'info': 1, 'warn': 2, 'error': 3} var logmessages = {verbose: [], info: [], warn: [], error: []} var factory = { name: 'test1', create: function (callback) { callback(null, {id: Math.floor(Math.random() * 1000)}) }, destroy: function (client) {}, max: 2, idleTimeoutMillis: 100, log: function (msg, level) { testlog(msg, level) } } var testlog = function (msg, level) { assert.ok(level in loglevels) logmessages[level].push(msg) } var pool = poolModule.Pool(factory) var pool2 = poolModule.Pool({ name: 'testNoLog', create: function (callback) { callback(null, {id: Math.floor(Math.random() * 1000)}) }, destroy: function (client) {}, max: 2, idleTimeoutMillis: 100 }) assert.equal(pool2.getName(), 'testNoLog') pool.acquire(function (err, obj) { assert.ifError(err) assert.equal(logmessages.verbose[0], 'createResource() - creating obj - count=1 min=0 max=2') assert.equal(logmessages.info[0], 'dispense() clients=1 available=0') logmessages.info = [] logmessages.verbose = [] pool2.borrow(function (err, obj) { assert.ifError(err) assert.equal(logmessages.info.length, 0) assert.equal(logmessages.verbose.length, 0) assert.equal(logmessages.warn.length, 0) }) }) }, 'removes from available objects on destroy': function (beforeExit) { var destroyCalled = false var factory = { name: 'test', create: function (callback) { callback(null, {}) }, destroy: function (client) { destroyCalled = true }, max: 2, idleTimeoutMillis: 100 } var pool = poolModule.Pool(factory) pool.acquire(function (err, obj) { assert.ifError(err) pool.destroy(obj) }) assert.equal(destroyCalled, true) assert.equal(pool.availableObjectsCount(), 0) }, 'removes from available objects on validation failure': function (beforeExit) { var destroyCalled = false var validateCalled = false var count = 0 var factory = { name: 'test', create: function (callback) { callback(null, {count: count++}) }, destroy: function (client) { destroyCalled = client.count }, validate: function (client) { validateCalled = true; return client.count > 0 }, max: 2, idleTimeoutMillis: 100 } var pool = poolModule.Pool(factory) pool.acquire(function (err, obj) { assert.ifError(err) pool.release(obj) assert.equal(obj.count, 0) pool.acquire(function (err, obj) { assert.ifError(err) pool.release(obj) assert.equal(obj.count, 1) }) }) assert.equal(validateCalled, true) assert.equal(destroyCalled, 0) assert.equal(pool.availableObjectsCount(), 1) }, 'removes from available objects on async validation failure': function (beforeExit) { var destroyCalled = false var validateCalled = false var count = 0 var factory = { name: 'test', create: function (callback) { callback(null, {count: count++}) }, destroy: function (client) { destroyCalled = client.count }, validateAsync: function (client, callback) { validateCalled = true; callback(client.count > 0) }, max: 2, idleTimeoutMillis: 100 } var pool = poolModule.Pool(factory) pool.acquire(function (err, obj) { assert.ifError(err) pool.release(obj) assert.equal(obj.count, 0) pool.acquire(function (err, obj) { assert.ifError(err) pool.release(obj) assert.equal(obj.count, 1) }) }) assert.equal(validateCalled, true) assert.equal(destroyCalled, 0) assert.equal(pool.availableObjectsCount(), 1) }, 'error on setting both validate functions': function (beforeExit) { var noop = function () {} var factory = { name: 'test', create: noop, destroy: noop, validate: noop, validateAsync: noop } try { poolModule.Pool(factory) } catch (err) { assert.equal(err.message, 'Only one of validate or validateAsync may be specified') } }, 'do schedule again if error occured when creating new Objects async': function (beforeExit) { var factory = { name: 'test', create: function (callback) { process.nextTick(function () { var err = new Error('Create Error') callback(err) }) }, destroy: function (client) {}, max: 1, idleTimeoutMillis: 100 } var getFlag = 0 var pool = poolModule.Pool(factory) pool.acquire(function () {}) pool.acquire(function (err, obj) { getFlag = 1 assert(err) assert.equal(pool.availableObjectsCount(), 0) }) beforeExit(function () { assert.equal(getFlag, 1) }) }, 'returns only valid object to the pool': function (beforeExit) { var pool = poolModule.Pool({ name: 'test', create: function (callback) { process.nextTick(function () { callback(null, { id: 'validId' }) }) }, destroy: function (client) {}, max: 1, idleTimeoutMillis: 100 }) pool.acquire(function (err, obj) { assert.ifError(err) assert.equal(pool.availableObjectsCount(), 0) assert.equal(pool.inUseObjectsCount(), 1) // Invalid release pool.release({}) assert.equal(pool.availableObjectsCount(), 0) assert.equal(pool.inUseObjectsCount(), 1) // Valid release pool.release(obj) assert.equal(pool.availableObjectsCount(), 1) assert.equal(pool.inUseObjectsCount(), 0) }) }, 'validate acquires object from the pool': function (beforeExit) { var pool = poolModule.Pool({ name: 'test', create: function (callback) { process.nextTick(function () { callback(null, { id: 'validId' }) }) }, validate: function (resource) { return true }, destroy: function (client) {}, max: 1, idleTimeoutMillis: 100 }) pool.acquire(function (err, obj) { assert.ifError(err) assert.equal(pool.availableObjectsCount(), 0) assert.equal(pool.inUseObjectsCount(), 1) }) }, 'validateAsync acquires object from the pool': function (beforeExit) { var pool = poolModule.Pool({ name: 'test', create: function (callback) { process.nextTick(function () { callback(null, { id: 'validId' }) }) }, validateAsync: function (resource, callback) { callback(true) }, destroy: function (client) {}, max: 1, idleTimeoutMillis: 100 }) pool.acquire(function (err, obj) { assert.ifError(err) assert.equal(pool.availableObjectsCount(), 0) assert.equal(pool.inUseObjectsCount(), 1) }) }, 'domain context is preserved on acquire callback': function (beforeExit) { var assertion_count = 0 var pool = poolModule.Pool({ name: 'test', create: function (cb) { cb(null, {}) }, destroy: function (client) {}, max: 2, idleTimeoutMillis: 1000 }) // bail on old node versions because domains didn't exist until v0.8 if (process.version < 'v0.8') { return } var domain = require('domain') function check (index) { var wrapDomain = domain.create() wrapDomain.index = index wrapDomain.run(function () { pool.acquire(function (err, client) { assert.ifError(err) assert.equal(domain.active.index, index) assertion_count++ setTimeout(function () { pool.release(client) }, 50) }) }) } // first two will work even without domain binding check(1) check(2) // third and on will fail without domain binding check(3) beforeExit(function () { assert.equal(assertion_count, 3) }) } }