text: 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)
      })
    }
  }
  