test_that("can be created and closed", {
  pool <- poolCreate(function() 1)
  checkCounts(pool, free = 1, taken = 0)
  expect_true(pool$valid)

  poolClose(pool)
  checkCounts(pool, free = 0, taken = 0)
  expect_false(pool$valid)
})

test_that("it requires a valid factory", {
  expect_snapshot(error = TRUE, {
    poolCreate(1)
    poolCreate(function(x) NULL)
  })
})

test_that("pool can't fetch or close after close", {
  pool <- poolCreate(function() 1)
  poolClose(pool)

  expect_snapshot(error = TRUE, {
    poolCheckout(pool)
    poolClose(pool)
  })
})

test_that("can fetch and release", {
  pool <- poolCreate(function() 1)
  defer(poolClose(pool))

  obj <- poolCheckout(pool)
  expect_equal(obj, 1, ignore_attr = TRUE)
  checkCounts(pool, free = 0, taken = 1)

  poolReturn(obj)
  checkCounts(pool, free = 1, taken = 0)
})

test_that("max size is enforced", {
  pool <- poolCreate(MockPooledObj$new, maxSize = 2)
  defer(poolClose(pool))

  obj1 <- poolCheckout(pool)
  obj2 <- poolCheckout(pool)

  expect_snapshot(poolCheckout(pool), error = TRUE)

  poolReturn(obj1)
  poolReturn(obj2)
})

test_that("idle objects are reaped", {
  pool <- poolCreate(MockPooledObj$new, idleTimeout = 0)
  defer(poolClose(pool))

  obj1 <- poolCheckout(pool)
  obj2 <- poolCheckout(pool)
  poolReturn(obj1)
  poolReturn(obj2)

  checkCounts(pool, free = 2, taken = 0)
  later::run_now() # force scheduler to run NOW
  checkCounts(pool, free = 1, taken = 0)
})

test_that("validates (only) when needed", {
  pool <- poolCreate(MockPooledObj$new, validationInterval = 0.1)
  defer(poolClose(pool))

  last_validated <- function(pool) {
    obj <- localCheckout(pool)
    pool_metadata(obj)$lastValidated
  }

  # Capture initial validation time
  last_validated_0 <- last_validated(pool)

  # After waiting less than validationInterval, validation time shouldn't change
  Sys.sleep(pool$validationInterval / 2)
  last_validated_1 <- last_validated(pool)
  expect_equal(last_validated_0, last_validated_1)

  # After waiting more than validationInterval, validation time should change
  Sys.sleep(pool$validationInterval)
  last_validated_2 <- last_validated(pool)
  expect_lt(last_validated_0, last_validated_2)
})

test_that("warns if validation fails once, creates new object and tries again", {
  pool <- poolCreate(MockPooledObj$new, validationInterval = 0.1)
  defer(poolClose(pool))

  check_valid_object <- function(x) {
    # Sneak into private methods
    pool[['.__enclos_env__']]$private$checkObjectValid(x)
  }

  # create object that will fail to validate
  badObject <- poolCheckout(pool)
  attr(badObject, "bad") <- TRUE
  Sys.sleep(pool$validationInterval + .1)

  # can't validate, so should create a new object
  expect_snapshot(obj <- check_valid_object(badObject))

  Sys.sleep(pool$validationInterval + .1)
  expect_identical(obj, check_valid_object(obj))
  # this implicitly returns badOjbect
  checkCounts(pool, free = 1, taken = 0)

  # now force all validations to fail so we get an error
  failOnValidate <<- TRUE
  defer(failOnValidate <<- FALSE)

  Sys.sleep(pool$validationInterval + .1)
  expect_snapshot(check_valid_object(obj), error = TRUE)

  # and since all objects have been destroyed the pool is empty
  checkCounts(pool, free = 0, taken = 0)
})

test_that("can't return the same object twice", {
  pool <- poolCreate(MockPooledObj$new)
  defer(poolClose(pool))

  obj <- poolCheckout(pool)
  poolReturn(obj)
  expect_snapshot(poolReturn(obj), error = TRUE)
})

test_that("poolClose() warns about taken objects, but they can still be returned", {
  pool <- poolCreate(MockPooledObj$new)

  obj <- poolCheckout(pool)
  expect_snapshot(poolClose(pool))

  poolReturn(obj)
})

test_that("warns if object can't be returned", {
  expect_snapshot({
    pool <- poolCreate(function() 1)
    obj <- poolCheckout(pool)
    rm(obj)
    . <- gc()
    poolClose(pool)
  })
})

test_that("poolReturn() errors if object is not valid", {
  expect_snapshot(poolReturn("x"), error = TRUE)
})

test_that("pool has useful print method", {
  pool <- poolCreate(function() 10)
  defer(poolClose(pool))

  expect_snapshot({
    pool

    x1 <- poolCheckout(pool)
    x2 <- poolCheckout(pool)
    pool

    poolReturn(x1)
    pool

    poolReturn(x2)
  })
})

test_that("empty pool has useful print method", {
  pool <- poolCreate(function() 10, minSize = 0)
  defer(poolClose(pool))

  expect_snapshot({
    pool
  })
})

# Failure modes -----------------------------------------------------------

test_that("useful warning if onDestroy fails", {
  pool <- poolCreate(MockPooledObj$new, idleTimeout = 0)
  defer(poolClose(pool))

  checkCounts(pool, free = 1, taken = 0)
  failOnDestroy <<- TRUE
  defer(failOnDestroy <<- FALSE)

  a <- poolCheckout(pool)
  b <- poolCheckout(pool)

  # since we're over minSize, returning `b` destroys it
  expect_snapshot({
    poolReturn(b)
    later::run_now()
  })

  checkCounts(pool, free = 0, taken = 1)
  poolReturn(a)
})

test_that("throws if onPassivate fails", {
  pool <- poolCreate(MockPooledObj$new)
  defer(poolClose(pool))

  obj <- poolCheckout(pool)
  failOnPassivate <<- TRUE
  defer(failOnPassivate <<- FALSE)

  expect_snapshot(poolReturn(obj), error = TRUE)
})

test_that("throws if onActivate fails", {
  pool <- poolCreate(MockPooledObj$new)
  defer(poolClose(pool))

  failOnActivate <<- TRUE
  defer(failOnActivate <<- FALSE)

  expect_snapshot(poolCheckout(pool), error = TRUE)
  checkCounts(pool, free = 0, taken = 0)
})

test_that("throws if onValidate fails", {
  pool <- poolCreate(MockPooledObj$new)
  defer(poolClose(pool))

  failOnValidate <<- TRUE
  defer(failOnValidate <<- FALSE)
  expect_snapshot(poolCheckout(pool), error = TRUE)
  checkCounts(pool, free = 0, taken = 0)
})
