tests/testthat/test-location-path.R

test_that("can construct a orderly_location_path object", {
  root <- create_temporary_root()
  loc <- orderly_location_path$new(root$path)
  expect_s3_class(loc, "orderly_location_path")
  dat <- loc$list()
  expect_equal(nrow(dat), 0)
  expect_s3_class(dat, "data.frame")
  expect_equal(names(dat), c("packet", "time", "hash"))
})


test_that("orderly_location_path requires existing directory", {
  path <- temp_file()
  expect_error(
    orderly_location_path$new(path),
    "Directory does not exist:")
})


test_that("orderly_location_path requires exact root", {
  root <- create_temporary_root()
  subdir <- file.path(root$path, "subdir")
  dir.create(subdir)
  expect_error(
    orderly_location_path$new(subdir),
    "Did not find existing orderly (or outpack) root in",
    fixed = TRUE)

  expect_silent(orderly_location_path$new(root$path))
})


test_that("orderly_location_path returns list of packet ids", {
  root <- create_temporary_root()
  path <- root$path
  loc <- orderly_location_path$new(path)

  ids <- vcapply(1:3, function(i) create_random_packet(root$path))

  dat <- loc$list()
  expect_s3_class(dat, "data.frame")
  expect_equal(dat$packet, ids)
  expect_s3_class(dat$time, "POSIXt")
  str <- vcapply(file.path(path, ".outpack", "metadata", ids), read_string)
  expect_equal(
    dat$hash,
    vcapply(str, hash_data, "sha256", USE.NAMES = FALSE))
})


test_that("orderly_location_path can return metadata", {
  root <- create_temporary_root()
  path <- root$path
  loc <- orderly_location_path$new(path)

  ids <- vcapply(1:3, function(i) create_random_packet(path))
  str <- setNames(
    vcapply(file.path(path, ".outpack", "metadata", ids), read_string),
    ids)

  expect_equal(loc$metadata(ids[[2]]), str[2])
  expect_equal(loc$metadata(ids), str)
  expect_equal(loc$metadata(rep(ids[[1]], 2)), str[c(1, 1)])
})


test_that("requesting nonexistant metadata is an error", {
  root <- create_temporary_root()
  path <- root$path

  loc <- orderly_location_path$new(path)
  ids <- vcapply(1:3, function(i) create_random_packet(path))

  errs <- c("20220317-125935-ee5fd50e", "20220317-130038-48ffb8ba")
  err1 <- expect_error(loc$metadata(errs[[1]]), "Some packet ids not found")
  expect_match(conditionMessage(err1), errs[[1]], fixed = TRUE)

  err2 <- expect_error(loc$metadata(errs), "Some packet ids not found")
  expect_equal(err2$body, set_names(errs, "*"))

  err3 <- expect_error(loc$metadata(c(ids[[1]], errs[[1]], ids[[2]])),
                       "Some packet ids not found")
  expect_equal(conditionMessage(err3), conditionMessage(err1))
})


test_that("can locate files from the store", {
  root <- create_temporary_root(use_file_store = TRUE)
  path <- root$path

  loc <- orderly_location_path$new(path)
  ids <- vcapply(1:3, function(i) create_random_packet(path))

  files <- outpack_metadata_core(ids[[1]], root)$files

  h <- files$hash[files$path == "data.rds"]
  dest <- temp_file()
  res <- loc$fetch_file(h, dest)
  expect_identical(res, dest)
  expect_identical(hash_file(res), h)
})


test_that("sensible error if file not found in store", {
  root <- create_temporary_root(use_file_store = TRUE)
  path <- root$path

  loc <- orderly_location_path$new(path)
  h <- "md5:c7be9a2c3cd8f71210d9097e128da316"
  dest <- temp_file()
  expect_error(
    loc$fetch_file(h, dest),
    "Hash 'md5:c7be9a2c3cd8f71210d9097e128da316' not found at location")
  expect_false(file.exists(dest))
})


test_that("Can find file from archive", {
  root <- create_temporary_root(use_file_store = TRUE)
  path <- root$path

  loc <- orderly_location_path$new(path)
  ids <- vcapply(1:3, function(i) create_random_packet(path))
  idx <- root$index$data()

  files <- idx$metadata[[1]]$files
  h <- files$hash[files$path == "data.rds"]
  dest <- temp_file()
  res <- loc$fetch_file(h, dest)
  expect_identical(res, dest)
  expect_identical(hash_file(dest), h)
})


test_that("sensible error if file not found in archive", {
  root <- create_temporary_root(use_file_store = FALSE)
  path <- root$path

  loc <- orderly_location_path$new(path)
  h <- "md5:c7be9a2c3cd8f71210d9097e128da316"
  dest <- temp_file()
  expect_error(
    loc$fetch_file(h, dest),
    "Hash 'md5:c7be9a2c3cd8f71210d9097e128da316' not found at location")
  expect_false(file.exists(dest))
})


test_that("can detect differences between locations when destination empty", {
  client <- create_temporary_root()
  ids <- create_random_packet_chain(client, 4)

  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)

  files <- lapply(ids, function(id) client$index$metadata(id)$files$hash)

  ## Simplest case; leaf node not known to the server.
  plan1 <- location_build_push_plan(ids[[1]], "server", client)
  expect_setequal(names(plan1), c("packet_id", "files"))
  expect_equal(plan1$packet_id, ids[[1]])
  expect_setequal(plan1$files, files[[1]])

  ## Whole tree:
  plan2 <- location_build_push_plan(ids[[4]], "server", client)
  expect_setequal(names(plan2), c("packet_id", "files"))
  expect_setequal(plan2$packet_id, ids)
  expect_setequal(plan2$files, unique(unlist(files, FALSE, FALSE)))

  ## Same if we use any of our ids explicitly:
  expect_equal(
    location_build_push_plan(ids, "server", client),
    location_build_push_plan(ids[[4]], "server", client))
  expect_equal(
    location_build_push_plan(ids[c(1, 4)], "server", client),
    location_build_push_plan(ids[[4]], "server", client))
})


test_that("Import complete tree via push into server", {
  client <- create_temporary_root()
  ids <- create_random_packet_chain(client, 4)

  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)

  plan <- orderly_location_push(ids[[4]], "server", root = client)

  idx_c <- client$index$data()
  idx_s <- server$index$data()

  expect_equal(idx_s$metadata, idx_c$metadata)
  expect_equal(idx_s$unpacked, idx_c$unpacked)
  expect_equal(idx_s$location$packet, idx_c$unpacked)
  expect_setequal(idx_s$location$hash, idx_c$location$hash)

  expect_setequal(plan$packet_id, ids)
  files_used <- lapply(ids, function(id) client$index$metadata(id)$files$hash)
  expect_setequal(plan$files, unique(unlist(files_used, FALSE, FALSE)))
})


test_that("Import packets into root with archive as well as store", {
  client <- create_temporary_root()
  ids <- create_random_packet_chain(client, 4)

  server <- create_temporary_root(use_file_store = TRUE,
                                  path_archive = "archive")
  orderly_location_add_path("server", path = server$path, root = client)

  plan <- orderly_location_push(ids[[4]], "server", root = client)

  expect_equal(
    sort(withr::with_dir(server$path, fs::dir_ls("archive", recurse = TRUE))),
    sort(withr::with_dir(client$path, fs::dir_ls("archive", recurse = TRUE))))
})


test_that("Prevent pushing things that would corrupt the store", {
  ## This can't actually happen without some deletion on the server I
  ## believe, which is going to require some race condition. But bugs
  ## could result in an incorrect plan being generated and these are
  ## the errors that would prevent the import going astray.
  client <- create_temporary_root()
  ids <- create_random_packet_chain(client, 4)

  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)

  id <- ids[[3]]
  str <- read_string(file.path(client$path, ".outpack", "metadata", id))
  hash <- hash_data(str, "sha256")

  expect_error(
    location_path_import_metadata(str, chartr("bcdef", "cdefa", hash), server),
    sprintf("Hash of metadata for '%s' does not match", id))
  expect_error(
    location_path_import_metadata(str, hash, server),
    sprintf("Can't import metadata for '%s', as files missing", id))

  ## Manually import the files:
  for (h in client$index$metadata(id)$files$hash) {
    location_path_import_file(find_file_by_hash(client, h), h, server)
  }
  expect_error(
    location_path_import_metadata(str, hash, server),
    sprintf("Can't import metadata for '%s', as dependencies missing", id))
})


test_that("Can only push into a root with a file store", {
  ## This could possibly be relaxed, but it's hard to stash files
  ## somewhere without the store. Really in this condition the
  ## "server" should be pulling.
  client <- create_temporary_root()
  ids <- create_random_packet_chain(client, 2)
  server <- create_temporary_root()
  orderly_location_add_path("server", path = server$path, root = client)
  expect_error(
    orderly_location_push(ids[[2]], "server", root = client),
    "Can't push files into this server, as it does not have a file store")
})


test_that("pushing twice does nothing", {
  client <- create_temporary_root()
  ids <- create_random_packet_chain(client, 4)
  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)
  plan1 <- orderly_location_push(ids[[4]], "server", root = client)
  plan2 <- orderly_location_push(ids[[4]], "server", root = client)
  expect_equal(plan2, list(packet_id = character(), files = character()))
})


test_that("push overlapping tree", {
  client <- create_temporary_root()
  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)

  id_base <- create_random_packet(server)
  orderly_location_fetch_metadata(root = client)
  suppressMessages(orderly_location_pull(id_base, root = client))

  ids <- create_random_packet_chain(client, 3, id_base)
  plan <- orderly_location_push(ids[[3]], "server", root = client)

  expect_setequal(plan$packet_id, ids)
  expect_setequal(names(server$index$data()$metadata), c(id_base, ids))
})


test_that("Push single packet", {
  client <- create_temporary_root()
  id <- create_random_packet(client)

  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)

  plan <- orderly_location_push(id, "server", root = client)

  idx_c <- client$index$data()
  idx_s <- server$index$data()

  expect_equal(idx_s$metadata, idx_c$metadata)
  expect_equal(idx_s$unpacked, idx_c$unpacked)
  expect_equal(idx_s$location$packet, idx_c$unpacked)
  expect_setequal(idx_s$location$hash, idx_c$location$hash)

  expect_equal(plan$packet_id, id)
  files_used <- lapply(id, function(id) client$index$metadata(id)$files$hash)
  expect_setequal(plan$files, unique(unlist(files_used, FALSE, FALSE)))
})


test_that("Can read metadata files with a trailing newline", {
  # Past versions of orderly wrote metadata files with a trailing newline
  # character, despite the fact that the newline was not included when hashing.
  #
  # This has been fixed by not writing the newline anymore, but for
  # compatibility we need to ensure we can still read those metadata files and
  # get a correct hash.

  root <- create_temporary_root()
  id <- create_random_packet(root)
  path <- file.path(root$path, ".outpack", "metadata", id)

  # Calling writeLines adds the trailing newline and mimicks the old orderly
  # behaviour. The size will be one or two bytes bigger than the actual data,
  # depending on whether the newline is `\n` or `\r\n`.
  old_size <- file.info(path)$size
  writeLines(read_string(path), path)
  expect_gte(file.info(path)$size, old_size + 1)

  # Reading the metadata from a location at that path correctly strips the
  # newline and hashes correctly.
  loc <- orderly_location_path$new(root$path)
  packets <- loc$list()
  data <- loc$metadata(id)
  expect_equal(nchar(data), old_size, ignore_attr = TRUE)

  expected_hash <- packets[packets$packet == id]$hash
  expect_no_error(hash_validate_data(data, expected_hash))
})


test_that("Fail to push sensibly if files have been changed", {
  client <- create_temporary_root()
  ids <- create_random_packet_chain(client, 4)

  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)

  ## Corrupt one file:
  path <- file.path(client$path, "archive", "b", ids[["b"]], "script.R")
  forcibly_truncate_file(path)

  expect_error(
    suppressMessages(orderly_location_push(ids[[4]], "server", root = client)),
    "Did not find suitable file, can't push this packet")
})


test_that("allow relative paths in path locations", {
  tmp <- withr::local_tempdir()
  a <- suppressMessages(orderly_init(file.path(tmp, "a")))
  b <- suppressMessages(orderly_init(file.path(tmp, "b")))
  ids <- vcapply(1:3, function(i) create_random_packet(b))
  withr::with_dir(a, orderly_location_add_path("b", path = "../b"))
  orderly_location_fetch_metadata(root = a)
  expect_equal(orderly_search(root = a, location = "b"), ids)
})


test_that("allow weird absolute paths in path locations", {
  tmp <- withr::local_tempdir()
  nms <- letters[1:3]
  root <- suppressMessages(
    set_names(lapply(nms, function(x) orderly_init(file.path(tmp, x))), nms))

  withr::with_dir(
    tmp, orderly_location_add_path("b", path = "../b", root = "a"))
  expect_equal(
    orderly_location_list(verbose = TRUE, root = root$a)$args[[2]]$path,
    "../b")

  fs::dir_create(file.path(root$a, "some/deep/path"))
  withr::with_dir(
    file.path(root$a, "some/deep/path"),
    orderly_location_add_path("c", path = "../c"))
  expect_equal(
    orderly_location_list(verbose = TRUE, root = root$a)$args[[3]]$path,
    "../c")
})


test_that("provide hint when wrong relative path given", {
  tmp <- withr::local_tempdir()
  tmp <- normalizePath(tmp)
  nms <- letters[1:3]
  root <- suppressMessages(
    set_names(lapply(nms, function(x) orderly_init(file.path(tmp, x))), nms))

  err <- expect_error(
    withr::with_dir(
      tmp, orderly_location_add_path("b", path = "b", root = "a")),
    "'path' must be given relative to the orderly root")
  expect_equal(err$body[[2]],
               "Consider passing '../b' instead")
})


test_that("Dry run does not push", {
  client <- create_temporary_root()
  id1 <- create_random_packet(client, parameters = list(a = 1))
  id2 <- create_random_packet(client, parameters = list(a = 2))
  id3 <- create_random_packet(client, parameters = list(a = 1))

  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)

  withr::local_options(orderly.quiet = FALSE)
  res <- evaluate_promise(
    orderly_location_push("parameter:a == 1", "server",
                          dry_run = TRUE, root = client))
  expect_length(res$result$packet_id, 2)
  expect_length(orderly_search(root = server), 0)
  expect_length(res$messages, 2)
  expect_match(res$messages[[1]], "Pushing 2 files for 2 packets")
  expect_match(res$messages[[2]], "Not making any changes, as 'dry_run = TRUE'")
})


test_that("Inform if query matches nothing", {
  client <- create_temporary_root()
  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)
  withr::local_options(orderly.quiet = FALSE)

  res1 <- evaluate_promise(
    orderly_location_push("parameter:a == 1", "server", root = client))
  expect_length(res1$messages, 2)
  expect_match(res1$messages[[1]], "Query returned no packets to push")
  expect_match(res1$messages[[2]], "Nothing to push, everything up to date")
  expect_equal(res1$result, list(packet_id = character(), files = character()))

  res2 <- evaluate_promise(
    orderly_location_push(character(), "server", root = client))
  expect_length(res2$messages, 1)
  expect_equal(res2$messages[[1]], res1$messages[[2]])
  expect_equal(res2$result, res1$result)
})


test_that("prevent pushing unknown packets", {
  client <- create_temporary_root()
  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)

  expect_error(
    orderly_location_push("20241023-131946-0260c975", "server", root = client),
    "Trying to push unknown packet: '20241023-131946-0260c975'")
})


test_that("pull metadata after push", {
  client <- create_temporary_root()
  id1 <- create_random_packet(client, parameters = list(a = 1))
  id2 <- create_random_packet(client, parameters = list(a = 2))
  id3 <- create_random_packet(client, parameters = list(a = 1))

  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)

  plan <- orderly_location_push("parameter:a == 1", "server", root = client)
  expect_length(orderly_search(location = "server", root = client), 2)
})


test_that("push where no files have changed, only metadata", {
  client <- create_temporary_root()
  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)

  id1 <- create_deterministic_packet(client)
  id2 <- create_deterministic_packet(client)

  orderly_location_push(id1, "server", root = client)
  withr::local_options(orderly.quiet = FALSE)
  res <- evaluate_promise(orderly_location_push(id2, "server", root = client))
  expect_equal(res$result, list(packet_id = id2, files = character()))
  expect_match(res$messages, "No files needed, all are available at location",
               all = FALSE)
})


test_that("pull where no files have changed, only metadata", {
  client <- create_temporary_root()
  server <- create_temporary_root(use_file_store = TRUE, path_archive = NULL)
  orderly_location_add_path("server", path = server$path, root = client)

  id1 <- create_deterministic_packet(server)
  orderly_location_fetch_metadata(root = client)
  orderly_location_pull(id1, root = client)

  id2 <- create_deterministic_packet(server)
  orderly_location_fetch_metadata(root = client)
  withr::local_options(orderly.quiet = FALSE)
  res <- evaluate_promise(orderly_location_pull(id2, root = client))
  expect_equal(res$result, id2)
  expect_match(res$messages,
               "All files available locally, no need to fetch any",
               all = FALSE)
})

Try the orderly package in your browser

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

orderly documentation built on Jan. 24, 2026, 1:07 a.m.