test_that("weakref with key and no value allows key to be GC'd", {
  # This is the case when a weakref is used like a weak pointer (with no
  # value).
  k <- env()
  w_finalized <- FALSE
  w <- new_weakref(key = k, finalizer = function(e) w_finalized <<- TRUE)

  expect_identical(wref_key(w), k)
  expect_identical(wref_value(w), NULL)
  expect_false(w_finalized)

  rm(k)
  gc()
  expect_identical(wref_key(w), NULL)
  expect_true(w_finalized)
})


test_that("key keeps value alive", {
  # Key and value: key keeps value alive
  k <- env()
  k_finalized <- FALSE
  reg.finalizer(k, function(e) {
    k_finalized <<- TRUE
  })

  v <- env()
  v$x <- "hello"
  v_finalized <- FALSE
  reg.finalizer(v, function(e) {
    v_finalized <<- TRUE
  })

  w_finalized <- FALSE
  w <- new_weakref(
    key = k,
    value = v,
    finalizer = function(e) {
      w_finalized <<- TRUE
    }
  )
  expect_identical(wref_key(w), k)
  expect_identical(wref_value(w), v)

  # As long as the key is reachable, the value stays alive.
  rm(v)
  gc()
  expect_false(v_finalized)
  expect_identical(wref_value(w)$x, "hello")

  # Even if the weakref object is unreachable, it still exists, so as long as the key is
  # reachable, it keeps the value alive.
  rm(w)
  gc()
  expect_false(v_finalized)
  expect_false(w_finalized)

  # When the key becomes unreachable, that allows the weakref and value to be
  # GC'd.
  rm(k)
  gc()
  expect_true(k_finalized)
  expect_true(v_finalized)
  expect_true(w_finalized)
})
