tests/testthat/test-ring-buffer-env.R

context("ring_buffer_env")

test_that("empty", {
  n <- 10
  buf <- ring_buffer_env(10)
  expect_equal(buf$size(), n)
  expect_identical(buf$size(), as.integer(n))

  expect_equal(buf$used(), 0L)
  expect_equal(buf$free(), 10L)
  expect_true(buf$is_empty())
  expect_false(buf$is_full())
  expect_equal(buf$head_pos(), 0L)
  expect_equal(buf$tail_pos(), 0L)
})

test_that("push", {
  n <- 10
  buf <- ring_buffer_env(10)
  m <- 4
  buf$push(1:m)
  expect_equal(buf$used(), m)
  expect_equal(buf$free(), n - m)

  expect_false(buf$is_empty())
  expect_false(buf$is_full())
  expect_equal(buf$head_pos(), m)
  expect_equal(buf$tail_pos(), 0L)

  expect_equal(buf$tail(), 1)
  expect_equal(buf$head(), m)
})

test_that("read", {
  n <- 10
  buf <- ring_buffer_env(10)
  m <- 4
  buf$push(1:m)

  expect_equal(buf$read(0), list())
  expect_equal(buf$read(m), as.list(1:m))
  expect_error(buf$read(m + 1), "Buffer underflow")
})

test_that("tail_offset", {
  n <- 10
  buf <- ring_buffer_env(10)
  m <- 4
  buf$push(1:m)

  expect_equal(buf$tail_offset(0), 1L)
  expect_equal(buf$tail_offset(1), 2L)
  expect_equal(buf$tail_offset(m - 1), m)
  expect_error(buf$tail_offset(m), "Buffer underflow")
})

test_that("head_offset", {
  n <- 10
  buf <- ring_buffer_env(n)
  m <- 4
  buf$push(1:m)

  expect_equal(buf$head_offset(0), m)
  expect_equal(buf$head_offset(1), m - 1)
  expect_equal(buf$head_offset(m - 1), 1)
  expect_error(buf$head_offset(m), "Buffer underflow")
})

test_that("take", {
  n <- 10
  buf <- ring_buffer_env(10)
  m <- 4
  buf$push(1:m)

  expect_equal(buf$take(0), list())
  expect_equal(buf$used(), m)

  expect_equal(buf$take(1), list(1))
  expect_equal(buf$used(), m - 1)

  expect_equal(buf$head_pos(), m)
  expect_equal(buf$tail_pos(), 1L)
  expect_equal(buf$tail(), 2)

  expect_equal(buf$take(m - 1), as.list(2:m))
  expect_equal(buf$head_pos(), m)
  expect_equal(buf$tail_pos(), m)
  expect_true(buf$is_empty())
  expect_false(buf$is_full())
})

test_that("take_head", {
  n <- 10
  buf <- ring_buffer_env(10)
  m <- 4
  buf$push(1:m)

  expect_equal(buf$read_head(0), list())
  expect_equal(buf$take_head(0), list())
  expect_equal(buf$used(), m)

  expect_equal(buf$read_head(1), list(m))
  expect_equal(buf$take_head(1), list(m))
  expect_equal(buf$used(), m - 1)

  expect_equal(buf$head_pos(), m - 1L)
  expect_equal(buf$tail_pos(), 0L)
  expect_equal(buf$tail(), 1)
  expect_equal(buf$head(), m - 1L)

  expect_equal(buf$read_head(m - 1), as.list(rev(seq_len(m - 1))))
  expect_equal(buf$take_head(m - 1), as.list(rev(seq_len(m - 1))))
  expect_equal(buf$head_pos(), 0L)
  expect_equal(buf$tail_pos(), 0L)
  expect_true(buf$is_empty())
  expect_false(buf$is_full())
})

test_that("fill buffer, then overflow", {
  n <- 10
  buf <- ring_buffer_env(10)
  buf$push(1:n)
  expect_true(buf$is_full())
  expect_equal(buf$used(), n)
  expect_identical(buf$.head, buf$.tail)

  expect_equal(buf$head_pos(), 0L) # wrapped...
  expect_equal(buf$tail_pos(), 0L) # hasn't moved yet

  ## All the data is here:
  expect_equal(buf$read(n), as.list(seq_len(n)))

  ## Add one more, causing an overflow:
  buf$push(n + 1)
  expect_true(buf$is_full())
  expect_equal(buf$used(), n)
  expect_identical(buf$.head, buf$.tail)

  expect_equal(buf$head_pos(), 1L)
  expect_equal(buf$tail_pos(), 1L)

  expect_equal(buf$read(n), as.list(seq_len(n) + 1L))
})

test_that("duplicate", {
  n <- 10
  buf <- ring_buffer_env(10)
  buf$push(1:12)
  buf$take(3)

  expect_equal(buf$head_pos(), 2)
  expect_equal(buf$tail_pos(), 5)
  expect_equal(buf$used(), 7)
  expect_equal(buf$size(), n)
  expect_equal(buf$read(buf$used()), as.list(6:12))

  cpy <- buf$duplicate()

  ## Source unchanged:
  for (x in list(buf, cpy)) {
    expect_equal(x$head_pos(), 2)
    expect_equal(x$tail_pos(), 5)
    expect_equal(x$used(), 7)
    expect_equal(x$size(), n)
    expect_equal(x$read(x$used()), as.list(6:12))
  }

  ## But we can move the two buffers independently.
  expect_equal(cpy$take(2), as.list(6:7))
  cpy$push(13)

  expect_equal(buf$head_pos(), 2)
  expect_equal(buf$tail_pos(), 5)
  expect_equal(buf$used(), 7)
  expect_equal(buf$size(), n)
  expect_equal(buf$read(buf$used()), as.list(6:12))

  expect_equal(cpy$head_pos(), 3)
  expect_equal(cpy$tail_pos(), 7)
  expect_equal(cpy$used(), 6)
  expect_equal(cpy$size(), n)
  expect_equal(cpy$read(cpy$used()), as.list(8:13))
})

test_that("copy zero", {
  n1 <- 20
  n2 <- 10
  buf1 <- ring_buffer_env(n1)
  buf2 <- ring_buffer_env(n2)

  buf1$push(1:n1)
  buf1$copy(buf2, 0L)

  expect_equal(buf1$head_pos(), 0)
  expect_equal(buf2$head_pos(), 0)
  expect_equal(buf1$tail_pos(), 0)
  expect_equal(buf2$tail_pos(), 0)
  expect_equal(buf1$used(), n1)
  expect_equal(buf2$used(), 0)
})

test_that("copy some", {
  n1 <- 20
  n2 <- 10
  buf1 <- ring_buffer_env(n1)
  buf2 <- ring_buffer_env(n2)

  buf1$push(1:n1)
  buf1$copy(buf2, 5L)

  expect_equal(buf1$head_pos(), 0)
  expect_equal(buf2$head_pos(), 5)
  expect_equal(buf1$tail_pos(), 5)
  expect_equal(buf2$tail_pos(), 0)
  expect_equal(buf1$used(), n1 - 5)
  expect_equal(buf2$used(), 5)
})

test_that("can't copy into self", {
  n <- 20
  buf <- ring_buffer_env(n)
  expect_error(buf$copy(buf, 0),
               "Can't copy a buffer into itself")
})

## Because we do things that create circular references, I want to
## check that R will delete everything appropriately.
test_that("destruction", {
  buf <- ring_buffer_env(10)
  buf$push(1:10)

  deleted <- integer(0)
  finaliser <- function(obj) {
    deleted <<- c(deleted, obj$data)
  }
  local({
    head <- buf$.head
    for (i in seq_len(buf$size())) {
      reg.finalizer(head, finaliser)
      head <- head$.next
    }
  })
  rm(buf)
  gc()

  expect_equal(length(deleted), 10L)
  expect_equal(sort(deleted), 1:10)
})

test_that("set", {
  buf <- ring_buffer_env(10)
  expect_null(buf$set(1, 10))
  expect_true(buf$is_full())
  expect_equal(buf$read(10), rep(list(1), 10))
})

test_that("no overflow", {
  buf <- ring_buffer_env(10, "error")
  expect_error(buf$push(1:11), "Buffer overflow")
  expect_equal(buf$used(), 0)
  expect_true(buf$.check_overflow)
  expect_true(buf$.prevent_overflow)

  buf$push(1:5)
  expect_error(buf$push(6:11), "Buffer overflow")
  expect_equal(buf$used(), 5)
  expect_equal(buf$size(), 10)
  expect_equal(as.list(buf), as.list(1:5))

  buf2 <- buf$duplicate()
  expect_true(buf2$.check_overflow)
  expect_true(buf2$.prevent_overflow)
  expect_error(buf2$push(6:11), "Buffer overflow")
  expect_equal(buf2$used(), 5)
  expect_equal(as.list(buf2), as.list(1:5))
})

test_that("grow", {
  ## First, try manually growing a buffer under various states.  This
  ## has slightly fewer moving parts than doing this reactively based
  ## on overflow.
  ##
  ## There are three scenarios to try here: empty, partially full and
  ## totally full.
  n <- 4
  e <- 3

  ## (1) empty
  buf <- ring_buffer_env(n)
  expect_null(buf$grow(e))
  expect_equal(buf$size(), n + e)
  expect_equal(buf$used(), 0)
  buf$push(seq_len(n + e))
  expect_equal(buf$read(n + e), as.list(seq_len(n + e)))

  ## (2) partially full
  buf <- ring_buffer_env(n)
  buf$push(1:2)
  buf$grow(e)
  expect_equal(buf$size(), n + e)
  expect_equal(buf$used(), 2)
  buf$push(e:(n + e))
  expect_equal(buf$read(n + e), as.list(seq_len(n + e)))

  ## (e) completely full
  buf <- ring_buffer_env(n)
  buf$push(1:n)
  buf$grow(e)
  expect_equal(buf$size(), n + e)
  expect_equal(buf$used(), n)
  buf$push(seq_len(e) + n)
  expect_equal(buf$read(n + e), as.list(seq_len(n + e)))
})

test_that("zero growth", {
  n <- 10
  buf <- ring_buffer_env(n)
  b <- random_bytes(n)
  buf$push(b)
  buf$grow(0)
  expect_equal(buf$size(), n)
  expect_equal(buf$read(n), as.list(b))
  expect_equal(buf$head_pos(), 0)
  expect_equal(buf$tail_pos(), 0)
})

test_that("grow on overflow", {
  buf <- ring_buffer_env(4, "grow")

  buf$push(1:10)
  expect_equal(buf$size(), 10)
  expect_true(buf$is_full())
  expect_equal(buf$read(10), as.list(1:10))

  expect_equal(buf$take(2), as.list(1:2))

  buf$push(11:15)
  expect_equal(buf$size(), 13)
  expect_equal(buf$read(13), as.list(3:15))

  buf$push(16:20)
  expect_equal(buf$size(), 18)
  expect_equal(buf$read(18), as.list(3:20))
})

test_that("invalid overflow option", {
  expect_error(ring_buffer_env(10, "g"),
               "Invalid value for 'on_overflow'")
  expect_error(ring_buffer_env(10, NA),
               "Invalid value for 'on_overflow'")
  expect_error(ring_buffer_env(10, "magic"),
               "Invalid value for 'on_overflow'")

  expect_error(ring_buffer_env(10, 1),
               "on_overflow must be a character")
  expect_error(ring_buffer_env(10, character(0)),
               "on_overflow must be a scalar")
})

test_that("format", {
  b <- ring_buffer_env(10)
  expect_false(any(grepl("\\.[a-z]+:", strsplit(format(b), "\n")[[1]])))
  class(b) <- c("tmp", "R6")
  expect_true(any(grepl("\\.[a-z]+:", strsplit(format(b), "\n")[[1]])))
})

test_that("mirror", {
  size <- 50

  rb1 <- ring_buffer_env(size)
  rb2 <- ring_buffer_env(size)

  bb1 <- random_bytes(floor(size / 2))
  bb2 <- random_bytes(floor(size / 3))
  rb1$push(bb1)
  rb2$push(bb2)

  expect_error(rb1$mirror(rb1),
               "Can't mirror a buffer into itself")
  expect_error(rb1$mirror(ring_buffer_bytes(size - 1)),
               "Can't mirror as buffers differ in their size (50 vs 49)",
               fixed = TRUE)

  expect_equal(rb1$read(rb1$used()), as.list(bb1))
  expect_equal(rb2$read(rb2$used()), as.list(bb2))

  rb1$mirror(rb2)
  expect_equal(rb1$read(rb1$used()), as.list(bb1))
  expect_equal(rb1$read(rb1$used()), as.list(bb1))
  expect_equal(rb1$head_pos(), rb2$head_pos())
  expect_equal(rb1$tail_pos(), rb2$tail_pos())
  expect_equal(rb1$used(), rb2$used())

  ## Now rotate the buffer and try again:
  rb2$push(seq_len(size + 5))
  rb2$take(4)
  bb3 <- rb2$read(rb2$used())

  rb2$mirror(rb1)
  expect_equal(rb1$read(rb1$used()), as.list(bb3))
  expect_equal(rb2$read(rb2$used()), as.list(bb3))
  expect_equal(rb1$head_pos(), rb2$head_pos())
  expect_equal(rb1$tail_pos(), rb2$tail_pos())
  expect_equal(rb1$used(), rb2$used())
})

test_that("reset", {
  rb <- ring_buffer_env(10)
  rb$push(1:3)
  rb$push(seq_len(rb$size()))

  rb$reset()
  expect_equal(rb$used(), 0)
  expect_true(rb$is_empty())
  expect_equal(rb$head_pos(), 0)
  expect_equal(rb$tail_pos(), 0)

  expect_equal(rb$.buffer$data, 8L)
  rb$reset(TRUE)
  expect_null(rb$.buffer$data)
})

test_that("typed errors", {
  b <- ring_buffer_env(10, on_overflow = "error")
  expect_error(b$push(1:20), "Buffer overflow")
  ans <- tryCatch(b$push(1:20), error = function(e) e)
  expect_is(ans, "ring_overflow")
  expect_equal(ans$free, 10)
  expect_equal(ans$requested, 20)
  expect_equal(tryCatch(b$push(1:20), ring_overflow = function(e) e), ans)

  expect_error(b$read(1), "Buffer underflow")
  ans <- tryCatch(b$read(1), error = function(e) e)
  expect_is(ans, "ring_underflow")
  expect_equal(ans$used, 0)
  expect_equal(ans$requested, 1)
  expect_equal(tryCatch(b$read(1), ring_underflow = function(e) e), ans)
})

test_that("head set/get/advance", {
  rb <- ring_buffer_env(10)
  d <- runif(1)
  rb$head_set(d)
  expect_equal(rb$head_data(), d)
  expect_true(rb$is_empty())

  rb$head_advance()
  expect_false(rb$is_empty())
  expect_equal(rb$used(), 1)
  expect_equal(rb$tail(), d)
})

test_that("store arbitrary objects", {
  rb <- ring_buffer_env(10)
  fit <- lm(mpg ~ cyl, mtcars)
  rb$push(fit, iterate = FALSE)
  ans <- rb$take(1)
  expect_equal(ans[[1]], fit)
})

Try the ring package in your browser

Any scripts or data that you put into this service are public.

ring documentation built on April 28, 2023, 5:08 p.m.