context("test-cancel.R")

test_that("Cancelling callbacks", {
  # Cancel with zero delay
  x <- 0
  cancel <- later(function() { x <<- x + 1 })
  later(function() { x <<- x + 2 })
  expect_true(cancel())
  run_now()
  expect_identical(x, 2)

  # Cancel with zero delay
  x <- 0
  cancel <- later(function() { x <<- x + 1 }, 1)
  run_now(0.25)
  expect_true(cancel())
  run_now(1)
  expect_identical(x, 0)

  # Make sure a cancelled callback doesn't interfere with others
  x <- 0
  later(function() { x <<- x + 1 }, 1)
  cancel <- later(function() { x <<- x + 2 }, 0.5)
  run_now()
  expect_true(cancel())
  run_now(2)
  expect_identical(x, 1)
})


test_that("Cancelled functions will be GC'd", {
  x <- 0
  cancel <- later(
    local({
      reg.finalizer(environment(), function(e) x <<- x + 1)
      function() message("foo")
    })
  )
  expect_true(cancel())
  gc()
  expect_identical(x, 1)
})


test_that("Cancelling executed or cancelled callbacks has no effect", {
  # Cancelling an executed callback
  x <- 0
  cancel <- later(function() { x <<- x + 1 })
  run_now()
  expect_false(cancel())
  run_now()
  expect_identical(x, 1)

  # Cancelling twice
  x <- 0
  cancel <- later(function() { x <<- x + 1 })
  expect_true(cancel())
  expect_false(cancel())
  run_now()
  expect_identical(x, 0)
})


test_that("Cancelling callbacks on temporary event loops", {
  with_temp_loop({
    # Cancelling an executed callback
    x <- 0
    cancel <- later(function() { x <<- x + 1 })
    run_now()
    expect_false(cancel())
    run_now()
    expect_identical(x, 1)
  })

  with_temp_loop({
    # Cancelling twice
    x <- 0
    cancel <- later(function() { x <<- x + 1 })
    expect_true(cancel())
    expect_false(cancel())
    run_now()
    expect_identical(x, 0)
  })

  with_temp_loop({
    # Make sure a cancelled callback doesn't interfere with others
    x <- 0
    later(function() { x <<- x + 1 }, 1)
    cancel <- later(function() { x <<- x + 2 }, 0.5)
    run_now()
    expect_true(cancel())
    run_now(2)
    expect_identical(x, 1)
  })

  # Canceling after an event loop handle has been destroyed: the underlying
  # data structure (in C++) will be deleted, along with the callbacks. This is
  # true because the loop does not have a parent.
  cancel <- NULL
  x <- 0
  with_temp_loop({
    cancel <- later(function() { x <<- x + 1 })
  })
  expect_false(cancel())
  expect_identical(x, 0)
})


test_that("Cancelling callbacks on persistent private loops without parent", {
  l1 <- create_loop(parent = NULL)
  l2 <- create_loop(parent = NULL)

  # Cancel from outside with_loop
  cancel <- NULL
  x <- 0
  with_loop(l1, {
    cancel <- later(function() { x <<- x + 1 })
  })
  expect_true(cancel())
  expect_false(cancel())
  with_loop(l1, run_now())
  expect_false(cancel())
  expect_identical(x, 0)


  # Make sure it doesn't interfere with other event loops
  with_loop(l1, {
    cancel <- later(function() { x <<- x + 1 })
  })
  with_loop(l2, {
    later(function() { x <<- x + 2 })
  })
  later(function() { x <<- x + 4 })
  expect_true(cancel())
  with_loop(l1, run_now())
  with_loop(l2, run_now())
  run_now()
  expect_identical(x, 6)


  # Cancelling on an explicitly destroyed loop returns FALSE
  l3 <- create_loop(parent = NULL)
  cancel <- NULL
  x <- 0
  with_loop(l3, {
    cancel <- later(function() { x <<- x + 1 })
  })
  destroy_loop(l3)
  expect_false(cancel())
  expect_identical(x, 0)
})

test_that("Cancelling callbacks on persistent private loops with parent", {
  # If the loop handle is GC'd but the loop _does have_ a parent, then the
  # underlying objects will not be destroyed right away, so the cancel() will
  # work.
  cancel <- NULL
  x <- 0
  local({
    l1 <- create_loop(parent = current_loop())
    cancel <<- later(function() { x <<- x + 1 }, loop = l1)
  })
  expect_true(cancel())
  expect_false(cancel())
  expect_identical(x, 0)
})

test_that("A canceler will not keep loop alive", {
  l <- create_loop(parent = NULL)
  finalized <- FALSE

  reg.finalizer(l, function(x) finalized <<- TRUE)
  cancel <- later(function() 1, loop = l)
  rm(l)
  gc()
  expect_true(finalized)
})

test_that("Canceling a callback from another a callback", {
  # Canceling a callback from another callback should work. Additionally, the
  # altered ordering of callbacks shouldn't prevent other callbacks from
  # running. In this test, #1 cancels 2 and 3, but we still expect 4 to run. If
  # we used the wrong algorithm for traversing the queue and canceling
  # callbacks, it would be possible for the cancellation of 2 and 3 to cause 4
  # to not run. This test ensures that we do it the right way.
  ran_2 <- FALSE
  ran_3 <- FALSE
  ran_4 <- FALSE
  with_temp_loop({
    cancel_1 <- later(function() { cancel_2(); cancel_3() })
    cancel_2 <- later(function() { ran_2 <<- TRUE })
    cancel_3 <- later(function() { ran_3 <<- TRUE })
    later(function() { ran_4 <<- TRUE })
    run_now()
  })

  expect_false(ran_2)
  expect_false(ran_3)
  expect_true(ran_4)
})
