diff --git a/DESCRIPTION b/DESCRIPTION index 497f740d..3f3fc46c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: pacta.workflow.utils Title: Utility functions for PACTA workflows -Version: 0.0.0.9001 +Version: 0.0.0.9002 Authors@R: c(person(given = "Alex", family = "Axthelm", @@ -18,8 +18,13 @@ RoxygenNote: 7.3.1 Imports: digest, jsonlite, - logger + logger, + pkgdepends, + pkgload Suggests: + devtools, + gert, + pak, testthat (>= 3.0.0), withr Config/testthat/edition: 3 diff --git a/R/export_manifest.R b/R/export_manifest.R index 13866cb2..f2ec13e8 100644 --- a/R/export_manifest.R +++ b/R/export_manifest.R @@ -39,7 +39,13 @@ create_manifest <- function( logger::log_debug("Creating metadata manifest") manifest_list <- list( input_files = get_file_metadata(input_files), - output_files = get_file_metadata(output_files) + output_files = get_file_metadata(output_files), + manifest_creation_datetime = format.POSIXct( + x = Sys.time(), + format = "%F %R", + tz = "UTC", + usetz = TRUE + ) ) return(manifest_list) } diff --git a/R/get_environment.R b/R/get_environment.R new file mode 100644 index 00000000..60af2216 --- /dev/null +++ b/R/get_environment.R @@ -0,0 +1,30 @@ +#' Get Environment information for manifest +#' +#' This function takes no arguments and returns a nested list, suitable for +#' inclusion in manifest export. +#' +#' @return nested list of file details, length the same as the input vector. +get_manifest_envirionment_info <- function() { + #: Envvars + return(invisible(NULL)) +} + +#' Get session information for manifest +#' +#' This function takes no arguments and returns a list, suitable for +#' inclusion in manifest export. +#' +#' @return list of session details, including R Version, platform, OS +#' (`running`), locale, timezone, and library paths. +get_r_session_info <- function() { + return( + list( + R.version = utils::sessionInfo()[["R.version"]], + platform = utils::sessionInfo()[["platform"]], + running = utils::sessionInfo()[["running"]], + locale = utils::sessionInfo()[["locale"]], + tzone = utils::sessionInfo()[["tzone"]], + libPaths = .libPaths() # nolint: undesirable_function_linter + ) + ) +} diff --git a/R/get_package_info.R b/R/get_package_info.R new file mode 100644 index 00000000..7da8aa57 --- /dev/null +++ b/R/get_package_info.R @@ -0,0 +1,147 @@ +#' Get package information for active packages +#' +#' This function takes a vector or (possibly nested) list of package names and +#' returns a nested list of package details, suitable for inclusion in manifest +#' export. +#' +#' @param packagelist vector or list of package names. Best left as default, +#' which includes the currently loaded and attached namespaces separated into +#' useful categories. +#' +#' @return nested list of file details, length 3, with top level keys being +#' `base`, `attached`, and `loaded`. Underneath those keys are lists of package +#' details, with the package names as keys, and further details as returned by +#' [get_individual_package_info()]. +#' @seealso [get_individual_package_info()] +get_package_info <- function( + packagelist = list( + base = utils::sessionInfo()[["basePkgs"]], + attached = names(utils::sessionInfo()[["otherPkgs"]]), + loaded = names(utils::sessionInfo()[["loadedOnly"]]) + ) +) { + log_debug("Getting package info.") + if (inherits(packagelist, "character")) { + out <- vapply( + X = packagelist, + FUN = function(x) { + list(x = get_individual_package_info(x)) + }, + FUN.VALUE = list(1L), + USE.NAMES = TRUE + ) + } else { + out <- vapply( + X = packagelist, + FUN = function(x) { + list( + x = get_package_info(x) + ) + }, + FUN.VALUE = list(1L), + USE.NAMES = TRUE + ) + } + return(out) +} + +#' Get package information for a package +#' +#' This function takes a single package name and returns a list of package +#' details, suitable for inclusion in manifest export. +#' +#' @param packagename Singular charater string of package name +#' +#' @return nested list of file details, length 11, with keys: +#' - `package`: The name of the package +#' - `version`: The version of the package +#' - `loaded_with_pkgload`: Is this package loaded with `pkgload`? (logical). +#' Useful for identifying local development versions +#' - `library`: The path of the library the package is installed in +#' - `library_index`: The index of the library in the `.libPaths()` vector +#' - `repository`: The repository the package was pulled from +#' - `platform`: The platform the package was built for +#' - `built`: Information about package build (relevant for binary packages) +#' - `remotetype`: The type of remote repository the package was pulled from +#' - `remotepkgref`: The reference used by `pak` to install the package +#' - `remoteref`: The reference of the package when it was pulled from REPO +#' - `remotesha`: the SHA-1 hash of the reference (if applicable) +get_individual_package_info <- function(packagename) { + if (length(packagename) != 1L || !is.character(packagename)) { + log_error("packagename must be a single string.") + # Early return + stop("packagename must be a single string.") + } + dev_package <- pkgload::is_dev_package(packagename) + if (dev_package) { + log_warn("Package \"{packagename}\" is a development package.") + log_warn("Package information may not be accurate.") + warning("Identifying development packages may not be accurate.") + package_dev_dir <- pkgload::pkg_path( + path = dirname(system.file("DESCRIPTION", package = packagename)) + ) + pkg_details <- list( + package = pkgload::pkg_name(package_dev_dir), + version = paste("DEV", pkgload::pkg_version(package_dev_dir)), + library = NA_character_, + library_index = NA_integer_, + repository = NA_character_, + platform = NA_character_, + built = NA_character_, + remotetype = "pkgload", + remotepkgref = normalizePath(package_dev_dir), + remoteref = NA_character_, + remotesha = NA_character_ + ) + } else { + if (packagename %in% utils::installed.packages()[, "Package"]) { + installed_index <- which( + utils::installed.packages()[, "Package"] == packagename + ) + installed_path <- utils::installed.packages()[installed_index, "LibPath"] + if (length(installed_path) > 1L) { + log_warn( + "Multiple installations of package \"{packagename}\" found: ", + "{installed_path}" + ) + log_warn("Using installation first on the search path.") + warning("Multiple installations of package found.") + } + lib_index <- min(which(.libPaths() == installed_path)) #nolint: undesirable_function_linter + lib <- .libPaths()[lib_index] #nolint: undesirable_function_linter + log_trace("Package \"{packagename}\" is installed at {lib}") + } else { + log_error("Package \"{packagename}\" is not installed.") + stop("Package is not installed.") + } + log_trace("Getting package info for {packagename}.") + pkg_details <- as.list( + pkgdepends::lib_status( + library = lib, + packages = packagename + ) + ) + pkg_details[["library_index"]] <- lib_index + } + details_list <- list( + package = pkg_details[["package"]], + version = pkg_details[["version"]], + loaded_with_pkgload = dev_package, + library = pkg_details[["library"]], + library_index = pkg_details[["library_index"]], + repository = pkg_details[["repository"]], + platform = pkg_details[["platform"]], + built = pkg_details[["built"]], + remotetype = pkg_details[["remotetype"]], + remotepkgref = pkg_details[["remotepkgref"]], + remoteref = pkg_details[["remoteref"]], + remotesha = pkg_details[["remotesha"]] + ) + clean_details_list <- lapply( + X = details_list, + FUN = function(x) { + ifelse(is.null(x), NA_character_, x) + } + ) + return(clean_details_list) +} diff --git a/man/get_individual_package_info.Rd b/man/get_individual_package_info.Rd new file mode 100644 index 00000000..b7c1bc29 --- /dev/null +++ b/man/get_individual_package_info.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_package_info.R +\name{get_individual_package_info} +\alias{get_individual_package_info} +\title{Get package information for a package} +\usage{ +get_individual_package_info(packagename) +} +\arguments{ +\item{packagename}{Singular charater string of package name} +} +\value{ +nested list of file details, length 11, with keys: +\itemize{ +\item \code{package}: The name of the package +\item \code{version}: The version of the package +\item \code{loaded_with_pkgload}: Is this package loaded with \code{pkgload}? (logical). +Useful for identifying local development versions +\item \code{library}: The path of the library the package is installed in +\item \code{library_index}: The index of the library in the \code{.libPaths()} vector +\item \code{repository}: The repository the package was pulled from +\item \code{platform}: The platform the package was built for +\item \code{built}: Information about package build (relevant for binary packages) +\item \code{remotetype}: The type of remote repository the package was pulled from +\item \code{remotepkgref}: The reference used by \code{pak} to install the package +\item \code{remoteref}: The reference of the package when it was pulled from REPO +\item \code{remotesha}: the SHA-1 hash of the reference (if applicable) +} +} +\description{ +This function takes a single package name and returns a list of package +details, suitable for inclusion in manifest export. +} diff --git a/man/get_manifest_envirionment_info.Rd b/man/get_manifest_envirionment_info.Rd new file mode 100644 index 00000000..d31e5094 --- /dev/null +++ b/man/get_manifest_envirionment_info.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_environment.R +\name{get_manifest_envirionment_info} +\alias{get_manifest_envirionment_info} +\title{Get Environment information for manifest} +\usage{ +get_manifest_envirionment_info() +} +\value{ +nested list of file details, length the same as the input vector. +} +\description{ +This function takes no arguments and returns a nested list, suitable for +inclusion in manifest export. +} diff --git a/man/get_package_info.Rd b/man/get_package_info.Rd new file mode 100644 index 00000000..910150e4 --- /dev/null +++ b/man/get_package_info.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_package_info.R +\name{get_package_info} +\alias{get_package_info} +\title{Get package information for active packages} +\usage{ +get_package_info( + packagelist = list(base = utils::sessionInfo()[["basePkgs"]], attached = + names(utils::sessionInfo()[["otherPkgs"]]), loaded = + names(utils::sessionInfo()[["loadedOnly"]])) +) +} +\arguments{ +\item{packagelist}{vector or list of package names. Best left as default, +which includes the currently loaded and attached namespaces separated into +useful categories.} +} +\value{ +nested list of file details, length 3, with top level keys being +\code{base}, \code{attached}, and \code{loaded}. Underneath those keys are lists of package +details, with the package names as keys, and further details as returned by +\code{\link[=get_individual_package_info]{get_individual_package_info()}}. +} +\description{ +This function takes a vector or (possibly nested) list of package names and +returns a nested list of package details, suitable for inclusion in manifest +export. +} +\seealso{ +\code{\link[=get_individual_package_info]{get_individual_package_info()}} +} diff --git a/man/get_r_session_info.Rd b/man/get_r_session_info.Rd new file mode 100644 index 00000000..2e3d7ad3 --- /dev/null +++ b/man/get_r_session_info.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_environment.R +\name{get_r_session_info} +\alias{get_r_session_info} +\title{Get session information for manifest} +\usage{ +get_r_session_info() +} +\value{ +list of session details, including R Version, platform, OS +(\code{running}), locale, timezone, and library paths. +} +\description{ +This function takes no arguments and returns a list, suitable for +inclusion in manifest export. +} diff --git a/tests/testthat/test-get_environment.R b/tests/testthat/test-get_environment.R new file mode 100644 index 00000000..35cc249b --- /dev/null +++ b/tests/testthat/test-get_environment.R @@ -0,0 +1,32 @@ +## save current settings so that we can reset later +threshold <- logger::log_threshold() +appender <- logger::log_appender() +layout <- logger::log_layout() +on.exit({ + ## reset logger settings + logger::log_threshold(threshold) + logger::log_layout(layout) + logger::log_appender(appender) +}) + +logger::log_appender(logger::appender_stdout) +logger::log_threshold(logger::FATAL) +logger::log_layout(logger::layout_simple) + +test_that("get_single_file_metadata processes CSV tables correctly", { + expect_identical( + get_r_session_info(), + list( + R.version = utils::sessionInfo()[["R.version"]], + platform = utils::sessionInfo()[["platform"]], + running = utils::sessionInfo()[["running"]], + locale = utils::sessionInfo()[["locale"]], + tzone = utils::sessionInfo()[["tzone"]], + libPaths = .libPaths() # nolint: undesirable_function_linter + ) + ) +}) + +testthat::expect_null( + get_manifest_envirionment_info() +) diff --git a/tests/testthat/test-get_individual_package_info.R b/tests/testthat/test-get_individual_package_info.R new file mode 100644 index 00000000..f25385c9 --- /dev/null +++ b/tests/testthat/test-get_individual_package_info.R @@ -0,0 +1,394 @@ +## save current settings so that we can reset later +threshold <- logger::log_threshold() +appender <- logger::log_appender() +layout <- logger::log_layout() +on.exit({ + ## reset logger settings + logger::log_threshold(threshold) + logger::log_layout(layout) + logger::log_appender(appender) +}) + +logger::log_appender(logger::appender_stdout) +logger::log_threshold(logger::FATAL) +logger::log_layout(logger::layout_simple) + +expect_package_info <- function( + package_info, + package_identical, + version_identical, + repository_match, + remotetype_identical, + remotepkgref_match, + remoteref_identical, + remotesha_identical, + loaded_with_pkgload_identical = FALSE +) { + testthat::expect_type(package_info, "list") + testthat::expect_named( + package_info, + expected = c( + "package", + "version", + "loaded_with_pkgload", + "library", + "library_index", + "repository", + "platform", + "built", + "remotetype", + "remotepkgref", + "remoteref", + "remotesha" + ) + ) + testthat::expect_identical( + object = package_info[["package"]], + expected = package_identical + ) + testthat::expect_identical( + object = package_info[["version"]], + expected = version_identical + ) + testthat::expect_identical( + object = package_info[["loaded_with_pkgload"]], + expected = loaded_with_pkgload_identical + ) + + if (loaded_with_pkgload_identical) { + testthat::expect_identical( + object = package_info[["library"]], + expected = NA_character_ + ) + testthat::expect_identical( + object = package_info[["library_index"]], + expected = NA_integer_ + ) + testthat::expect_identical( + object = package_info[["platform"]], + expected = NA_character_ + ) + } else { + testthat::expect_in( + object = package_info[["library"]], + .libPaths() #nolint: undesirable_function_linter + ) + testthat::expect_gt( + object = package_info[["library_index"]], + expected = 0L + ) + testthat::expect_lte( + object = package_info[["library_index"]], + expected = length(.libPaths()) #nolint: undesirable_function_linter + ) + testthat::expect_match( + object = package_info[["platform"]], + regexp = R.version[["platform"]] + ) + } + + testthat::expect_identical( + object = package_info[["library"]], + expected = .libPaths()[package_info[["library_index"]]] #nolint: undesirable_function_linter + ) + testthat::expect_type( + object = package_info[["library_index"]], + type = "integer" + ) + if (is.na(repository_match)) { + testthat::expect_identical( + package_info[["repository"]], + expected = repository_match + ) + } else { + testthat::expect_match( + object = package_info[["repository"]], + regexp = repository_match + ) + } + testthat::expect_false( + is.null(x = package_info[["built"]]) + ) + testthat::expect_identical( + object = package_info[["remotetype"]], + expected = remotetype_identical + ) + if (is.na(remotepkgref_match)) { + testthat::expect_identical( + package_info[["remotepkgref"]], + expected = remotepkgref_match + ) + } else { + testthat::expect_match( + object = package_info[["remotepkgref"]], + # gsub is used to make windows path work + regexp = gsub( + x = remotepkgref_match, + pattern = "\\", + replacement = "\\\\", + fixed = TRUE + ) + ) + } + testthat::expect_identical( + object = package_info[["remoteref"]], + remoteref_identical + ) + testthat::expect_identical( + object = package_info[["remotesha"]], + remotesha_identical + ) +} + +test_that("get_individual_package_info collects information for CRAN packages correctly", { #nolint: line_length_linter + expect_package_info( + package_info = get_individual_package_info("digest"), + package_identical = "digest", + version_identical = as.character(utils::packageVersion("digest")), + repository_match = "^(CRAN|RSPM)$", #GH Actions installs from RSPM, not CRAN + remotetype_identical = "standard", + remotepkgref_match = "^digest$", + remoteref_identical = "digest", + remotesha_identical = as.character(utils::packageVersion("digest")) + ) +}) + +test_that("get_individual_package_info collects information for base packages correctly", { #nolint: line_length_linter + expect_package_info( + package_info = get_individual_package_info("utils"), + package_identical = "utils", + version_identical = paste( + R.version[["major"]], + R.version[["minor"]], + sep = "." + ), + repository_match = NA_character_, #GH Actions installs from RSPM, not CRAN + remotetype_identical = NA_character_, + remotepkgref_match = NA_character_, + remoteref_identical = NA_character_, + remotesha_identical = NA_character_ + ) +}) + +with_local_install <- function(new_lib, package, code) { + cache_dir <- withr::local_tempdir() + withr::local_envvar(.new = c(R_USER_CACHE_DIR = cache_dir)) + withr::local_libpaths(new_lib, action = "prefix") + testthat::capture_output( #make pak quiet + pak::pak(package, lib = normalizePath(new_lib)) + ) + eval(code) +} + +test_that("get_individual_package_info collects information for local packages correctly", { #nolint: line_length_linter + testthat::skip_on_cran() + testthat::skip_if_offline() + dest_dir <- normalizePath(withr::local_tempdir()) + dl <- gert::git_clone( + url = "https://github.com/yihui/rmini.git", #nolint: nonportable_path_linter + path = dest_dir, + verbose = FALSE + ) + new_lib <- normalizePath(withr::local_tempdir()) + with_local_install(new_lib, paste0("local::", dest_dir), { + package_info <- get_individual_package_info("rmini") + expect_package_info( + package_info, + package_identical = "rmini", + version_identical = "0.0.4", + repository_match = NA_character_, + remotetype_identical = "local", + remotepkgref_match = paste0("^local::", dest_dir, "$"), + remoteref_identical = NA_character_, + remotesha_identical = NA_character_ + ) + expect_identical( + package_info[["library"]], + normalizePath(new_lib, winslash = "/") + ) + }) +}) + +test_that("get_individual_package_info collects information for GitHub packages correctly", { #nolint: line_length_linter + testthat::skip_on_cran() + testthat::skip_if_offline() + new_lib <- normalizePath(withr::local_tempdir()) + package_info <- with_local_install(new_lib, "yihui/rmini", { #nolint: nonportable_path_linter + package_info <- get_individual_package_info("rmini") + expect_package_info( + package_info, + package_identical = "rmini", + version_identical = "0.0.4", + repository_match = NA_character_, + remotetype_identical = "github", + remotepkgref_match = "^yihui/rmini$", #nolint: nonportable_path_linter + remoteref_identical = "HEAD", + remotesha_identical = "f839b7327c4cb422705b9f3b7c5ffc87555d98e2" + ) + expect_identical( + package_info[["library"]], + normalizePath(new_lib, winslash = "/") + ) + }) +}) + +test_that("get_individual_package_info collects information for packages loaded with pkgload correctly", { #nolint: line_length_linter + testthat::skip_on_cran() + testthat::skip_if_offline() + testthat::skip_if_not_installed("pkgload") + dest_dir <- normalizePath(withr::local_tempdir()) + dl <- gert::git_clone( + url = "https://github.com/yihui/rmini.git", #nolint: nonportable_path_linter + path = dest_dir, + verbose = FALSE + ) + loaded <- pkgload::load_all(dest_dir, quiet = TRUE) + withr::defer({ + pkgload::unload(package = "rmini") + }) + testthat::expect_warning( + object = { + package_info <- get_individual_package_info("rmini") + expect_package_info( + package_info, + package_identical = "rmini", + version_identical = "DEV 0.0.4", + loaded_with_pkgload_identical = TRUE, + repository_match = NA_character_, + remotetype_identical = "pkgload", + remotepkgref_match = paste0("^", dest_dir, "$"), + remoteref_identical = NA_character_, + remotesha_identical = NA_character_ + ) + expect_identical( + package_info[["remotepkgref"]], + normalizePath(dest_dir) + ) + }, + "^Identifying development packages may not be accurate.$" + ) +}) + +test_that("get_individual_package_info collects information for packages loaded with devtools correctly", { #nolint: line_length_linter + testthat::skip_on_cran() + testthat::skip_if_offline() + testthat::skip_if_not_installed("devtools") + dest_dir <- normalizePath(withr::local_tempdir()) + dl <- gert::git_clone( + url = "https://github.com/yihui/rmini.git", #nolint: nonportable_path_linter + path = dest_dir, + verbose = FALSE + ) + loaded <- devtools::load_all(dest_dir, quiet = TRUE) + withr::defer({ + devtools::unload(package = "rmini") + }) + testthat::expect_warning( + object = { + package_info <- get_individual_package_info("rmini") + expect_package_info( + package_info, + package_identical = "rmini", + version_identical = "DEV 0.0.4", + loaded_with_pkgload_identical = TRUE, + repository_match = NA_character_, + remotetype_identical = "pkgload", + remotepkgref_match = paste0("^", dest_dir, "$"), + remoteref_identical = NA_character_, + remotesha_identical = NA_character_ + ) + expect_identical( + package_info[["remotepkgref"]], + normalizePath(dest_dir) + ) + }, + "^Identifying development packages may not be accurate.$" + ) +}) + +test_that("get_individual_package_info errors for multiple packages", { + expect_error( + get_individual_package_info(c("digest", "jsonlite")) + ) +}) + +test_that("get_individual_package_info errors for package that doesn't exist", { + expect_error( + get_individual_package_info("this_package_does_not_exist") + ) +}) + +test_that("get_individual_package_info errors for empty string", { + expect_error( + get_individual_package_info("") + ) +}) + +test_that("get_individual_package_info errors for no arguments", { + expect_error( + get_individual_package_info(), + "^argument \"packagename\" is missing, with no default$" + ) +}) + +test_that("get_individual_package_info gets correct libpath and version of multiple installs", { #nolint: line_length_linter + testthat::skip_on_cran() + testthat::skip_if_offline() + new_lib <- normalizePath(withr::local_tempdir()) + newer_lib <- normalizePath(withr::local_tempdir()) + expect_warning( + with_local_install(new_lib, "yihui/rmini", { #nolint: nonportable_path_linter + with_local_install(newer_lib, "yihui/rmini@308d27d", { #nolint: nonportable_path_linter + package_info <- get_individual_package_info("rmini") + expect_package_info( + package_info, + package_identical = "rmini", + version_identical = "0.0.3", # Note: not latest version + repository_match = NA_character_, + remotetype_identical = "github", + remotepkgref_match = "^yihui/rmini@308d27d$", #nolint: nonportable_path_linter + remoteref_identical = "308d27d", + remotesha_identical = "308d27ddb0b45fda34fc259492145834d72849a9" + ) + expect_identical( + package_info[["library"]], + normalizePath(newer_lib, winslash = "/") + ) + expect_identical( + package_info[["library_index"]], + 1L + ) + }) + }) + ) +}) + +test_that("get_individual_package_info gets correct libpath for lower search priority", { #nolint: line_length_linter + testthat::skip_on_cran() + testthat::skip_if_offline() + new_lib <- normalizePath(withr::local_tempdir()) + newer_lib <- normalizePath(withr::local_tempdir()) + with_local_install(new_lib, "yihui/rmini", { #nolint: nonportable_path_linter + with_local_install(newer_lib, "digest", { + package_info <- get_individual_package_info("rmini") + expect_package_info( + package_info, + package_identical = "rmini", + version_identical = "0.0.4", + repository_match = NA_character_, + remotetype_identical = "github", + remotepkgref_match = "^yihui/rmini$", #nolint: nonportable_path_linter + remoteref_identical = "HEAD", + remotesha_identical = "f839b7327c4cb422705b9f3b7c5ffc87555d98e2" + ) + expect_identical( + package_info[["library"]], + normalizePath(new_lib, winslash = "/") + ) + expect_identical( + package_info[["library_index"]], + 2L + ) + }) + }) +}) diff --git a/tests/testthat/test-get_package_info.R b/tests/testthat/test-get_package_info.R new file mode 100644 index 00000000..23682723 --- /dev/null +++ b/tests/testthat/test-get_package_info.R @@ -0,0 +1,236 @@ +## save current settings so that we can reset later +threshold <- logger::log_threshold() +appender <- logger::log_appender() +layout <- logger::log_layout() +on.exit({ + ## reset logger settings + logger::log_threshold(threshold) + logger::log_layout(layout) + logger::log_appender(appender) +}) + +logger::log_appender(logger::appender_stdout) +logger::log_threshold(logger::FATAL) +logger::log_layout(logger::layout_simple) + +expect_warning_if_any_pkgload <- function(object, regexp) { + attached_pkgs <- names(utils::sessionInfo()[["otherPkgs"]]) + has_pkgload <- any( + vapply( + X = attached_pkgs, + FUN = pkgload::is_dev_package, + FUN.VALUE = logical(1L) + ) + ) + if (has_pkgload) { + testthat::expect_warning(object = object, regexp = regexp) + } else { + # note not using testthat::expect_no_warning(object = object), since there + # are often warnings on CI systems about multiple installations of the + # tested package + object + } +} + +test_that("get_package_info outputs correct structure for defaults", { + expect_warning_if_any_pkgload( + object = { + package_info <- get_package_info() + expect_named( + object = package_info, + expected = c("base", "attached", "loaded") + ) + expect_named( + object = package_info[["base"]], + expected = utils::sessionInfo()[["basePkgs"]] + ) + expect_named( + object = package_info[["attached"]], + expected = names(utils::sessionInfo()[["otherPkgs"]]) + ) + expect_named( + object = package_info[["loaded"]], + expected = names(utils::sessionInfo()[["loadedOnly"]]) + ) + # Check that the structure of the package_info is correct + expect_true( + object = all( + vapply( + X = package_info, + FUN = function(x) { + all( + vapply( + X = x, + FUN = function(x) { + all( + names(x) == c( + "package", + "version", + "loaded_with_pkgload", + "library", + "library_index", + "repository", + "platform", + "built", + "remotetype", + "remotepkgref", + "remoteref", + "remotesha" + ) + ) + }, + FUN.VALUE = logical(1L) + ) + ) + }, + FUN.VALUE = logical(1L) + ) + ) + ) + }, + "^Identifying development packages may not be accurate.$" + ) +}) + +test_that("get_package_info outputs expected value for single package", { + package_info <- get_package_info("digest") + expect_identical( + package_info, + list( + digest = get_individual_package_info("digest") + ) + ) +}) + +test_that("get_package_info outputs expected value for simple vector", { + package_info <- get_package_info(c("digest", "utils")) + expect_identical( + package_info, + list( + digest = get_individual_package_info("digest"), + utils = get_individual_package_info("utils") + ) + ) +}) + +test_that("get_package_info outputs expected value for named vector", { + package_info <- get_package_info(c(foo = "digest", bar = "utils")) + expect_identical( + package_info, + list( + foo = get_individual_package_info("digest"), + bar = get_individual_package_info("utils") + ) + ) +}) + +test_that("get_package_info outputs unamed list for unnamed list input", { + package_info <- get_package_info(list("digest", "utils")) + expect_identical( + package_info, + list( + list(digest = get_individual_package_info("digest")), + list(utils = get_individual_package_info("utils")) + ) + ) +}) + +test_that("get_package_info outputs expected value for simple named list", { + package_info <- get_package_info(list(foo = "digest", bar = "utils")) + expect_identical( + package_info, + list( + foo = list(digest = get_individual_package_info("digest")), + bar = list(utils = get_individual_package_info("utils")) + ) + ) +}) + +test_that("get_package_info outputs expected value for list with mixed nesting", { #nolint: line_length_linter + package_info <- get_package_info( + list( + foo = list(list("digest")), + bar = list("digest"), + baz = "utils" + ) + ) + expect_identical( + package_info, + list( + foo = list(list(list(digest = get_individual_package_info("digest")))), + bar = list(list(digest = get_individual_package_info("digest"))), + baz = list(utils = get_individual_package_info("utils")) + ) + ) +}) + +empty_named_list <- list() +names(empty_named_list) <- character(0L) + +test_that("get_package_info with empty character args returns empty named lists", { #nolint: line_length_linter + expect_identical( + object = get_package_info(list( + base = character(), + attached = character(), + loaded = character() + )), + expected = list( + base = empty_named_list, + attached = empty_named_list, + loaded = empty_named_list + ) + ) +}) + +test_that("get_package_info with empty args returns empty lists", { + expect_identical( + object = get_package_info(list( + base = c(), # nolint: unnecessary_concatenation_linter + attached = c(), # nolint: unnecessary_concatenation_linter + loaded = c() # nolint: unnecessary_concatenation_linter + )), + expected = list( + base = list(), + attached = list(), + loaded = list() + ) + ) +}) + +test_that("get_package_info with NULL args returns empty lists", { + expect_identical( + object = get_package_info(list( + base = NULL, + attached = NULL, + loaded = NULL + )), + expected = list( + base = list(), + attached = list(), + loaded = list() + ) + ) +}) + +test_that("get_package_info respects order", { + digest_info <- get_individual_package_info("digest") + utils_info <- get_individual_package_info("utils") + expect_identical( + object = get_package_info( + list( + base = c("digest", "utils"), + attached = c("utils", "digest") + ) + ), + expected = list( + base = list( + digest = digest_info, + utils = utils_info + ), + attached = list( + utils = utils_info, + digest = digest_info + ) + ) + ) +})