tests/testthat/test-read-delim.R

test_that("btread infers types and matches read.csv", {
  for (d in list(iris, mtcars, ChickWeight)) {
    p <- tempfile(fileext = ".csv")
    write.csv(d, p, row.names = FALSE)
    a <- btread(p)
    b <- read.csv(p, stringsAsFactors = FALSE)
    expect_equal(a, b, ignore_attr = TRUE)
  }
})

test_that("btread handles column types", {
  p <- tempfile(fileext = ".csv")
  df <- data.frame(i = 1:3L, x = c(1.5, 2, NA), b = c(TRUE, FALSE, NA),
                   s = c("a", "b", NA), stringsAsFactors = FALSE)
  write.csv(df, p, row.names = FALSE)
  r <- btread(p)
  expect_type(r$i, "integer")
  expect_type(r$x, "double")
  expect_type(r$b, "logical")
  expect_type(r$s, "character")
  expect_identical(r$x, df$x)
  expect_identical(r$b, df$b)
})

test_that("btread quoting: embedded delimiter, quote, newline", {
  p <- tempfile(fileext = ".csv")
  # binary connection so the embedded newline stays a bare \n on Windows too
  con <- file(p, "wb")
  writeLines(c('a,b', '"x,y",1', '"he said ""hi""",2', '"multi', 'line",3'), con, sep = "\n")
  close(con)
  r <- btread(p)
  expect_equal(nrow(r), 3L)
  expect_identical(r$a, c("x,y", 'he said "hi"', "multi\nline"))
  expect_identical(r$b, 1:3L)
})

test_that("btread: no header, delimiter sniffing, skip, comment, n_max", {
  p <- tempfile(fileext = ".txt")
  writeLines(c("# note", "1;2;3", "4;5;6", "7;8;9"), p)
  r <- btread(p, header = FALSE, comment = "#")
  expect_identical(names(r), c("V1", "V2", "V3"))
  expect_identical(r$V1, c(1L, 4L, 7L))

  r2 <- btread(p, header = FALSE, comment = "#", n_max = 2)
  expect_equal(nrow(r2), 2L)

  p2 <- tempfile(fileext = ".csv")
  writeLines(c("skipme", "a,b", "1,2"), p2)
  expect_identical(btread(p2, skip = 1)$a, 1L)
})

test_that("btread col_select keeps only requested columns", {
  p <- tempfile(fileext = ".csv")
  write.csv(iris, p, row.names = FALSE)
  expect_named(btread(p, col_select = c("Sepal.Length", "Species")),
               c("Sepal.Length", "Species"))
  expect_named(btread(p, col_select = c(1L, 5L)),
               c("Sepal.Length", "Species"))
})

test_that("btread col_types override", {
  p <- tempfile(fileext = ".csv")
  writeLines(c("a,b,c", "1,2,3", "4,5,6"), p)
  r <- btread(p, col_types = c("double", "character", "skip"))
  expect_type(r$a, "double")
  expect_type(r$b, "character")
  expect_named(r, c("a", "b"))
})

test_that("btread lazy mode returns correct values", {
  p <- tempfile(fileext = ".csv")
  df <- data.frame(a = 1:500L, b = rnorm(500), c = 500:1L)
  write.csv(df, p, row.names = FALSE)
  r <- btread(p, lazy = TRUE)
  expect_equal(sum(r$a), sum(df$a))
  expect_equal(r$b, df$b)
  expect_equal(r$c, df$c)
  expect_s3_class(r, "data.frame")
})

test_that("btread lazy mode returns ALTREP character columns that behave", {
  n <- 5000L
  df <- data.frame(
    id = seq_len(n),
    s1 = sample(c("alpha", "beta,x", NA, "gamma"), n, replace = TRUE),
    v  = rnorm(n),
    s2 = sprintf("k%04d", sample(500L, n, replace = TRUE)),
    stringsAsFactors = FALSE
  )
  p <- tempfile(fileext = ".csv")
  btwrite(df, p)

  eager <- btread(p)
  lazy  <- btread(p, lazy = TRUE)

  expect_type(lazy$s1, "character")
  expect_identical(lazy$s1, eager$s1)
  expect_identical(lazy$s2, eager$s2)
  expect_identical(paste0(lazy$s2, "!")[1:5], paste0(eager$s2, "!")[1:5])
  expect_equal(sum(nchar(lazy$s2)), sum(nchar(eager$s2)))

  # ALTSTRING stays API-compliant by exposing element access without a raw
  # writable data pointer: older R refuses the assignment, newer R materialises
  # the column first. Either is safe; a silent corruption is not.
  res <- tryCatch({ lazy$s1[2] <- "ZZ"; "materialised" },
                  error = function(e) conditionMessage(e))
  expect_true(identical(res, "materialised") || grepl("data pointer", res))
  if (identical(res, "materialised")) expect_identical(lazy$s1[2], "ZZ")

  # serialises without the mapping
  f <- tempfile()
  saveRDS(btread(p, lazy = TRUE), f)
  expect_equal(as.data.frame(readRDS(f)), as.data.frame(eager), ignore_attr = TRUE)

  # col_select through the lazy path keeps only requested columns unparsed
  sel <- btread(p, lazy = TRUE, col_select = c("id", "s2"))
  expect_named(sel, c("id", "s2"))
  expect_identical(sel$s2, eager$s2)
})

test_that("btread: NA strings and all-NA column", {
  p <- tempfile(fileext = ".csv")
  writeLines(c("a,b,c", "1,NA,", "2,x,", "3,NA,"), p)
  r <- btread(p, na = c("NA", ""))
  expect_true(is.na(r$b[1]))
  expect_true(all(is.na(r$c)))
})

test_that("btwrite round-trips through btread", {
  p <- tempfile(fileext = ".csv")
  df <- data.frame(
    i = c(1L, NA, 3L),
    x = c(1.25, NA, 3.5),
    b = c(TRUE, FALSE, NA),
    s = c("plain", "with,comma", 'with"quote'),
    f = factor(c("lo", "hi", "lo")),
    stringsAsFactors = FALSE
  )
  btwrite(df, p)
  r <- btread(p)
  expect_identical(r$i, df$i)
  expect_identical(r$x, df$x)
  expect_identical(r$b, df$b)
  expect_identical(r$s, df$s)
  expect_identical(r$f, as.character(df$f))
})

test_that("btwrite append does not repeat the header", {
  p <- tempfile(fileext = ".csv")
  btwrite(head(iris, 2), p)
  btwrite(iris[3:4, ], p, append = TRUE)
  expect_equal(nrow(btread(p)), 4L)
})

test_that("btread parallel indexer matches serial on a large file", {
  n <- 2e5L
  df <- data.frame(a = seq_len(n), b = runif(n), g = rep_len(letters, n))
  p <- tempfile(fileext = ".csv")
  btwrite(df, p)
  expect_gt(file.size(p), 2^20)               # large enough for the MT path

  r1 <- btread(p, n_threads = 1)
  r8 <- btread(p, n_threads = 8)
  expect_identical(r1, r8)
  expect_equal(r8$a, df$a)
  expect_equal(r8$b, df$b)
  expect_identical(r8$g, df$g)

  # n_max + header through the parallel path
  r <- btread(p, n_threads = 8, n_max = 1000)
  expect_equal(nrow(r), 1000L)
  expect_equal(r$a, df$a[1:1000])

  # no trailing newline
  raw <- readBin(p, "raw", file.size(p))
  if (raw[length(raw)] == as.raw(0x0a)) raw <- raw[-length(raw)]
  p2 <- tempfile(fileext = ".csv"); writeBin(raw, p2)
  r2 <- btread(p2, n_threads = 8)
  expect_equal(nrow(r2), n)
  expect_equal(r2$a[n], df$a[n])
})

test_that("btread reads gzip input", {
  p <- tempfile(fileext = ".csv")
  write.csv(head(mtcars), p, row.names = FALSE)
  pg <- paste0(p, ".gz")
  con <- gzfile(pg, "wb"); writeLines(readLines(p), con); close(con)
  r <- btread(pg)
  expect_equal(nrow(r), 6L)
  expect_equal(r$mpg, head(mtcars$mpg), ignore_attr = TRUE)
})

Try the basetable package in your browser

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

basetable documentation built on Sept. 18, 2026, 9:13 a.m.