tests/testthat/test-download.R

test_that("we avoid downloading files twice", {
  skip_on_cran()

  if (!renv_download_method() %in% c("curl", "wget"))
    skip("required downloader not available")

  url <- "https://cloud.r-project.org/src/contrib/Archive/sourcetools/sourcetools_0.1.0.tar.gz"
  destfile <- renv_scope_tempfile("renv-download-", fileext = ".tar.gz")

  # download once and check file metadata
  download(url, destfile, quiet = TRUE)
  before <- renv_file_info(destfile)$mtime

  # download again and check the file info hasn't changed
  download(url, destfile, quiet = TRUE)
  after <- renv_file_info(destfile)$mtime

  # check that they're the same.
  expect_identical(before, after)

})

test_that("we can successfully tweak the user agent string", {

  utils <- asNamespace("utils")

  ok <-
    is.function(utils$makeUserAgent) &&
    identical(formals(utils$makeUserAgent), pairlist(format = TRUE))

  if (!ok)
    return(NULL)

  headers <- c("Key" = "Value")

  before <- utils$makeUserAgent
  expect_true(renv_download_default_agent_scope_impl(headers = headers))
  after <- utils$makeUserAgent

  expect_identical(
    paste0(before(format = TRUE), "Key: Value\r\n"),
    after(format = TRUE)
  )

  expect_identical(before(format = FALSE), after(format = FALSE))

})

test_that("renv_download_default_agent_scope_impl resets itself", {

  before <- asNamespace("utils")$makeUserAgent
  local(renv_download_default_agent_scope_impl(c("Key" = "Value")))
  expect_equal(asNamespace("utils")$makeUserAgent, before)

})


test_that("we can successfully download files with different downloaders", {
  skip_on_cran()
  skip_on_os("windows")

  # download a small sample file
  url <- "https://cloud.r-project.org/src/base/THANKS"
  destfile <- renv_scope_tempfile("r-thanks-")
  method <- renv_download_method()
  download.file(url, destfile = destfile, quiet = TRUE, method = method)
  thanks <- readLines(destfile)

  if (nzchar(Sys.which("curl"))) local({
    renv_scope_envvars(RENV_DOWNLOAD_FILE_METHOD = "curl")
    destfile <- renv_scope_tempfile("r-curl-thanks-")
    download(url, destfile, quiet = TRUE)
    expect_equal(readLines(destfile), thanks)
  })

  if (renv_platform_windows()) local({
    renv_scope_envvars(RENV_DOWNLOAD_FILE_METHOD = "wininet")
    destfile <- renv_scope_tempfile("r-wininet-thanks-")
    download(url, destfile, quiet = TRUE)
    expect_equal(readLines(destfile), thanks)
  })

  if (capabilities("libcurl") %||% FALSE) local({
    renv_scope_envvars(RENV_DOWNLOAD_FILE_METHOD = "libcurl")
    destfile <- renv_scope_tempfile("r-libcurl-thanks-")
    download(url, destfile, quiet = TRUE)
    expect_equal(readLines(destfile), thanks)
  })

  # TODO: fails on winbuilder
  # if (nzchar(Sys.which("wget"))) local({
  #   renv_scope_envvars(RENV_DOWNLOAD_FILE_METHOD = "wget")
  #   destfile <- renv_scope_tempfile("r-wget-thanks-")
  #   download(url, destfile, quiet = TRUE)
  #   expect_equal(readLines(destfile), thanks)
  # })

})

test_that("downloads work with file URIs", {

  renv_tests_scope()

  repos <- getOption("repos")[["CRAN"]]
  url <- file.path(repos, "src/contrib/PACKAGES")

  destfile <- renv_scope_tempfile("packages-")
  download(url, destfile = destfile)

  expect_true(file.exists(!!destfile))

})

test_that("downloads work with UNC paths on Windows", {
  skip_on_cran()
  skip_if_not(renv_platform_windows())

  renv_tests_scope()

  # get path to repos PACKAGES file
  repos <- getOption("repos")[["CRAN"]]
  base <- sub("^file:/*", "", repos)
  url <- file.path(base, "src/contrib/PACKAGES")
  norm <- renv_path_normalize(url)

  # create server-style path to localhost
  unc <- sub("^([a-zA-Z]):", "//localhost/\\1$", norm)
  expect_true(file.exists(unc))

  destfile <- renv_scope_tempfile("packages-")

  urls <- c(unc, paste0("file:", unc))
  for (url in urls) {
    download(unc, destfile)
    expect_true(file.exists(destfile))
    unlink(destfile)
  }

})

test_that("we can check that a URL is available", {
  skip_on_cran()
  skip_if_not_installed("webfakes")

  app <- webfakes::new_app_process(webfakes::httpbin_app())

  url <- paste0(app$url(), "/bytes/100")
  expect_true(renv_download_available(url))

  # also test the different methods
  expect_true(renv_download_available_headers(url))
  expect_true(renv_download_available_range(url))
  expect_true(renv_download_available_fallback(url))
})

test_that("download failures are reported if destfile not writable", {
  skip_on_cran()
  skip_on_os("windows")

  tdir <- tempfile("renv-forbidden-")
  dir.create(tdir, mode = "0000")
  expect_true(file.exists(tdir))

  tfile <- tempfile("renv-download-", tmpdir = tdir)
  expect_error(
    download(url = "https://cran.r-project.org/", destfile = tfile, quiet = TRUE)
  )

  Sys.chmod(tdir, mode = "0755")
  download(url = "https://cran.r-project.org/", destfile = tfile, quiet = TRUE)
  expect_true(file.exists(tfile))

  unlink(tdir, recursive = TRUE)

})

test_that("renv_curl_version returns a valid numeric_version", {
  skip_if_not(nzchar(Sys.which("curl")))

  version <- renv_curl_version()
  expect_s3_class(version, "numeric_version")
  expect_true(version > "0.0.0")
})

test_that("renv_download_parallel_method respects renv_download_method", {

  # when download method is curl and version supports --parallel
  local({
    renv_scope_envvars(RENV_DOWNLOAD_METHOD = "curl")
    version <- catch(renv_curl_version())
    has_parallel <- !inherits(version, "error") && version >= "7.66.0"
    expected <- if (has_parallel) "curl" else "sequential"
    expect_equal(renv_download_parallel_method(), expected)
  })

  # when download method is libcurl
  local({
    renv_scope_envvars(RENV_DOWNLOAD_METHOD = "libcurl")
    expected <- if (getRversion() >= "4.5.0") "libcurl" else "sequential"
    expect_equal(renv_download_parallel_method(), expected)
  })

  # when download method is something else, always sequential
  local({
    renv_scope_envvars(RENV_DOWNLOAD_METHOD = "wininet")
    expect_equal(renv_download_parallel_method(), "sequential")
  })

})

test_that("renv_download_curl_config_text generates valid config", {

  text <- renv_download_curl_config_text(
    url      = "https://example.com/pkg.tar.gz",
    destfile = "/tmp/pkg.tar.gz",
    type     = NULL,
    headers  = c("X-Custom" = "value")
  )

  expect_true(any(grepl("location", text)))
  expect_true(any(grepl("fail", text)))
  expect_true(any(grepl("silent", text)))
  expect_true(any(grepl("url", text)))
  expect_true(any(grepl("output", text)))
  expect_true(any(grepl("X-Custom: value", text)))

  # HEAD request adds extra flags
  head_text <- renv_download_curl_config_text(
    url      = "https://example.com/pkg.tar.gz",
    destfile = "/tmp/pkg.tar.gz",
    type     = NULL,
    headers  = NULL,
    request  = "HEAD"
  )

  expect_true(any(grepl("head", head_text)))
  expect_true(any(grepl("include", head_text)))

})

test_that("renv_download_parallel works with curl backend", {

  skip_on_cran()
  skip_if_not_installed("webfakes")
  skip_if_not(nzchar(Sys.which("curl")))

  version <- catch(renv_curl_version())
  skip_if(inherits(version, "error") || version < "7.66.0",
          "curl >= 7.66.0 required for --parallel")

  app <- webfakes::new_app_process(webfakes::httpbin_app())

  urls <- paste0(app$url(), "/bytes/", c(100, 200, 300))
  names(urls) <- c("a", "b", "c")
  destfiles <- vapply(names(urls), function(nm) {
    tempfile(paste0("renv-parallel-curl-", nm, "-"))
  }, character(1L))
  types <- rep("unknown", 3L)

  renv_scope_envvars(RENV_DOWNLOAD_METHOD = "curl")
  ok <- renv_download_parallel(urls, destfiles, types)

  expect_true(all(ok))
  expect_equal(names(ok), names(urls))
  for (i in seq_along(destfiles)) {
    expect_true(file.exists(destfiles[[i]]))
    expect_equal(file.size(destfiles[[i]]), c(100, 200, 300)[[i]])
  }

  unlink(destfiles)

})

test_that("renv_download_parallel works with libcurl backend", {

  skip_on_cran()
  skip_if_not_installed("webfakes")
  skip_if_not(getRversion() >= "4.5.0", "R >= 4.5.0 required for vectorized libcurl")
  skip_if_not(capabilities("libcurl"), "libcurl not available")

  app <- webfakes::new_app_process(webfakes::httpbin_app())

  urls <- paste0(app$url(), "/bytes/", c(50, 150))
  names(urls) <- c("x", "y")
  destfiles <- vapply(names(urls), function(nm) {
    tempfile(paste0("renv-parallel-libcurl-", nm, "-"))
  }, character(1L))
  types <- rep("unknown", 2L)

  renv_scope_envvars(RENV_DOWNLOAD_METHOD = "libcurl")
  ok <- renv_download_parallel(urls, destfiles, types)

  expect_true(all(ok))
  expect_equal(names(ok), names(urls))
  for (i in seq_along(destfiles)) {
    expect_true(file.exists(destfiles[[i]]))
    expect_equal(file.size(destfiles[[i]]), c(50, 150)[[i]])
  }

  unlink(destfiles)

})

test_that("renv_download_parallel_sequential works as fallback", {
  skip_on_cran()
  skip_if_not_installed("webfakes")

  app <- webfakes::new_app_process(webfakes::httpbin_app())

  urls <- paste0(app$url(), "/bytes/", c(80, 160))
  names(urls) <- c("p", "q")
  destfiles <- vapply(names(urls), function(nm) {
    tempfile(paste0("renv-parallel-seq-", nm, "-"))
  }, character(1L))
  types <- rep("unknown", 2L)

  ok <- renv_download_parallel_sequential(urls, destfiles, types)

  expect_true(all(ok))
  expect_equal(names(ok), names(urls))
  for (df in destfiles)
    expect_true(file.exists(df))

  unlink(destfiles)

})

test_that("renv_download_parallel reports failure for bad URLs", {
  skip_on_cran()
  skip_if_not_installed("webfakes")

  app <- webfakes::new_app_process(webfakes::httpbin_app())

  urls <- c(
    good = paste0(app$url(), "/bytes/100"),
    bad  = paste0(app$url(), "/status/404")
  )
  destfiles <- vapply(names(urls), function(nm) {
    tempfile(paste0("renv-parallel-fail-", nm, "-"))
  }, character(1L))
  types <- rep("unknown", 2L)

  ok <- renv_download_parallel_sequential(urls, destfiles, types)

  expect_true(ok[["good"]])
  expect_false(ok[["bad"]])

  unlink(destfiles)

})

Try the renv package in your browser

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

renv documentation built on March 25, 2026, 5:07 p.m.