tests/testthat/helper-setup.R

#' Get a list of data base connections to test on
#' @param skip_backends (`character()`)\cr
#'   List of connection types to not return connections for.
#' @return
#'   If you run your tests locally, it returns a list of connections corresponding to conn_list and conn_args
#'   If you run your tests on GitHub, it return a list of connection corresponding to the environment variables.
#'   i.e. the GitHub workflows will configure the testing back ends
#' @importFrom rlang `:=`
#' @noRd
get_test_conns <- function(skip_backends = NULL) {

  # Locally use rlang's (without this, it may not be bound)
  `:=` <- rlang::`:=`

  # Check if we run remotely
  running_locally <- !identical(Sys.getenv("CI"), "true")

  # Define list of connections to check
  if (running_locally) {

    # Define our local connection backends
    conn_list <- list(
      # Backend string = package::function
      "SQLite" = "RSQLite::SQLite"
    )

    # Define our local connection arguments
    conn_args <- list(
      # Backend string = list(named args)
      "SQLite" = list(dbname = file.path(tempdir(), "SQLite.SQLite"))
    )

    # Define post connection commands to run
    conn_post_connect <- list()

  } else {

    # Use the connection configured by the remote
    conn_list <- tibble::lst(!!Sys.getenv("BACKEND") := !!Sys.getenv("BACKEND_DRV"))

    # Use the connection configured by the remote
    conn_args <- tibble::lst(!!Sys.getenv("BACKEND") := Sys.getenv("BACKEND_ARGS"))
    conn_args <- purrr::discard(conn_args, ~ identical(., ""))
    conn_args <- purrr::map(conn_args, ~ eval(parse(text = .)))

    # Use the connection configured by the remote
    conn_post_connect <- tibble::lst(!!Sys.getenv("BACKEND") := Sys.getenv("BACKEND_POST_CONNECT"))
    conn_post_connect <- purrr::discard(conn_post_connect, ~ identical(., ""))
    conn_post_connect <- purrr::map(conn_post_connect, ~ eval(parse(text = .)))

  }

  # Early return if no connections are defined
  if (length(conn_list) == 0) {
    return(list())
  }

  # Parse any conn_args stored in CONN_ARGS_JSON
  conn_args_json <- jsonlite::fromJSON(Sys.getenv("CONN_ARGS_JSON", unset = "{}"))

  # Combine all arguments
  backends <- unique(c(names(conn_list), names(conn_args), names(conn_args_json)))
  conn_args <- purrr::map(backends, ~ c(purrr::pluck(conn_args, .), purrr::pluck(conn_args_json, .)))
  names(conn_args) <- backends


  get_driver <- function(x = character(), ...) {                                                                        # nolint: object_usage_linter
    if (!grepl(".*::.*", x)) {
      stop(
        "Package must be specified with namespace (e.g. RSQLite::SQLite)!\n",
        "Received: ",
        x,
        call. = FALSE
      )
    }
    parts <- strsplit(x, "::", fixed = TRUE)[[1]]

    # Skip unavailable packages
    if (!rlang::is_installed(parts[1])) {
      message("Library ", parts[1], " not available!")
      return(NULL)
    }

    return(getExportedValue(parts[1], parts[2])())
  }

  # Check all conn_args have associated entry in conn_list
  checkmate::assert_subset(names(conn_args), names(conn_list))

  # Open connections
  drivers <- purrr::map(names(conn_list), ~ do.call(get_driver, list(x = purrr::pluck(conn_list, .))))
  names(drivers) <- names(conn_list)
  drivers <- purrr::discard(drivers, is.null)

  test_conn_args <- purrr::map(
    names(drivers),
    ~ c(list("drv" = purrr::pluck(drivers, .)), purrr::pluck(conn_args, .))
  )

  test_conns <- purrr::map(
    test_conn_args,
    ~ do.call(SCDB::get_connection, args = .)
  )
  names(test_conns) <- names(drivers)
  test_conns <- purrr::discard(test_conns, is.null)

  # Skip backends if given
  test_conns <- purrr::walk(
    test_conns,
    ~ {
      if (checkmate::test_multi_class(., purrr::pluck(skip_backends, .default = ""))) {
        DBI::dbDisconnect(.)
      }
    }
  )
  test_conns <- purrr::discard(
    test_conns,
    ~ checkmate::test_multi_class(., purrr::pluck(skip_backends, .default = ""))
  )

  # Run post_connect commands on the connections
  purrr::iwalk(
    test_conns,
    function(conn, conn_name) {
      purrr::walk(purrr::pluck(conn_post_connect, conn_name), ~ DBI::dbExecute(conn, .))
    }
  )

  # Inform the user about the tested back ends:
  msg <- paste(sep = "\n",
    "#####",
    "Following backends will be tested:",
    paste("  ", names(test_conns), collapse = "\n"),
    "####"
  )

  # Message the user only once within this session
  rlang::inform(
    message = msg,
    .frequency = "once",
    .frequency_id = msg
  )

  return(test_conns)
}


#' Parse checkmate assertions for testthat compatibility
#' @description
#'   The error messages generated by `checkmate` are formatted to look nicely in the console by the
#'   addition of `*` and `\n` characters.
#'
#'   This means that checking these errors with `testthat::expect_error()` will often fail or will be harder to read
#'   in the test since we need to manually insert `*` and `\n` to the comparison pattern to match the error message.
#'
#'   This helper function intercepts the `checkmate` error message and removes the `*` and `\n` characters to allow for
#'   human readable error checking.
#' @return
#'   The checkmate error without `*` and `\n` characters.
#' @noRd
checkmate_err_msg <- function(expr) {
  tryCatch(
    expr,
    error = function(e) {
      msg <- e$message
      msg <- stringr::str_remove_all(msg, stringr::fixed("\n *"))
      msg <- stringr::str_remove_all(msg, stringr::fixed("* "))

      stop(simpleError(message = msg))                                                                                  # nolint: condition_call_linter
    }
  )
}

Try the diseasystore package in your browser

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

diseasystore documentation built on April 4, 2025, 5:56 a.m.