# constants and helper functions for unit tests. Used by:
# - test-new-model.R
# - test-copy-model-from.R
# - test-modify-model-field.R
# - test-new-model.R
# - test-run-log.R
# - test-summary-log.R
# - test-config-log.R
# - test-model-summaries.R
# - test-utils.R
# - test-get-path-from-object.R
# - test-get-based-on.R
# - maybe others...
# define constants
REF_DIR <- system.file("test-refs", package = "bbr")
ABS_MODEL_DIR <- system.file("model", "nonmem", "basic", package = "bbr")
MOD_ID <- "1"
MODEL_DIR <- fs::path_rel(ABS_MODEL_DIR, getwd()) %>% as.character()
MOD1_PATH <- file.path(MODEL_DIR, MOD_ID)
MOD1 <- read_model(MOD1_PATH)
# if we're on Metworx or running in CI, run the summary
if (!Sys.getenv("METWORX_VERSION") == "" || Sys.getenv("CI") == "true") {
withr::with_options(list(bbr.bbi_exe_path = read_bbi_path()), {
SUM1 <- model_summary(MOD1)
})
}
NEW_MOD2 <- file.path(MODEL_DIR, "2")
NEW_MOD3 <- file.path(MODEL_DIR, "3")
BATCH_PARAM_TEST_DIR <- file.path(MODEL_DIR, "test_batch_params")
MODEL_DIR_X <- fs::path_rel(system.file("model", "nonmem", "complex", package = "bbr"), getwd()) %>% as.character()
# file names and file paths
OUTPUT_DIR <- MOD1_PATH
CTL_FILENAME <- ctl_ext(MOD_ID)
YAML_TEST_FILE <- as.character(glue::glue("{MOD1_PATH}.yaml"))
CTL_TEST_FILE <- as.character(glue::glue("{MOD1_PATH}.ctl"))
MOD_TEST_FILE <- as.character(glue::glue("{MOD1_PATH}.mod"))
LST_TEST_FILE <- as.character(glue::glue("{MOD1_PATH}/{MOD_ID}.lst"))
GRD_TEST_FILE <- as.character(glue::glue("{MOD1_PATH}/{MOD_ID}.grd"))
EXT_TEST_FILE <- as.character(glue::glue("{MOD1_PATH}/{MOD_ID}.ext"))
OUTPUT_FILE <- file.path(MOD1_PATH, "OUTPUT")
DATA_TEST_FILE <- as.character(fs::path_norm(file.path(REF_DIR, "..", "extdata", "acop.csv")))
DATA_TEST_FIRST_LINE <- "id,time,mdv,evid,dv,amt,sex,wt,etn,num"
DATA_TEST_COLS <- length(unlist(stringr::str_split(DATA_TEST_FIRST_LINE, ",")))
DATA_TEST_ROWS <- 799
DATA_TEST_ROWS_IGNORE <- DATA_TEST_ROWS - 20
TAB_FILE <- "1.tab"
MOD1_TABLE_FILES <- c(TAB_FILE, "1par.tab", "1first1.tab", "1first2.tab", "1first3.tab", "1dups.tab")
TAB_NEW_COLS <- 7
PARTAB_NEW_COLS <- 5
LEVEL2_SUBDIR <- "level2"
LEVEL2_DIR <- file.path(MODEL_DIR, LEVEL2_SUBDIR)
LEVEL2_MOD <- file.path(LEVEL2_DIR, MOD_ID)
ORIG_DESC <- "original acop model"
NEW_DESC <- "new description"
DESC_IN_CTL <- "PK model 1 cmt base"
ORIG_TAGS <- c("acop tag", "other tag")
NEW_TAGS <- c("new tag 1", "new tag 2")
NEW_NOTES <- c("I like it", "Actually I don't like it")
EXTRA_NOTE <- "Oh wait I do like it"
NEW_TEXT1 <- c("naw", "paw")
NEW_TEXT2 <- c("all", "done")
SUMMARY_REF_FILE <- file.path(REF_DIR, "1_summary_obj.R")
PARAM_REF_FILE <- file.path(REF_DIR, "1_param_table.R")
NM_SUM_CLASS_LIST <- c(NM_SUM_CLASS, BBI_PARENT_CLASS, "list")
NM_MOD_CLASS_LIST <- c(NM_MOD_CLASS, BBI_BASE_MODEL_CLASS, BBI_PARENT_CLASS, "list")
PROC_CLASS_LIST <- c(PROC_CLASS, "list")
PROC_HELP_STR <- as.character(glue(" {read_bbi_path()} --help"))
SUMS_LIST_NAMES_REF <- c("absolute_model_path", "bbi_summary", "error_msg", "needed_fail_flags")
SUM_NAMES_REF <- c("absolute_model_path", "run_details", "run_heuristics", "parameters_data",
"parameter_names", "ofv", "condition_number", "shrinkage_details")
if (test_bbi_version(read_bbi_path(), .min_version = "3.2.0")) {
SUM_NAMES_REF <- c(SUM_NAMES_REF, "success")
}
NO_LST_ERR_MSG <- "Unable to locate `.lst` file.*NONMEM output folder"
NOT_FINISHED_ERR_MSG <- if (test_bbi_version(read_bbi_path(), .min_version = "3.2.0")) {
"model_summary.*modeling run has not finished"
} else {
"nonmem_summary.*modeling run has not finished"
}
MOD1_ABS_PATH <- fs::path_norm(file.path(getwd(), tools::file_path_sans_ext(YAML_TEST_FILE))) %>% as.character()
MOD2_ABS_PATH <- fs::path_norm(file.path(getwd(), NEW_MOD2)) %>% as.character()
MOD3_ABS_PATH <- fs::path_norm(file.path(getwd(), NEW_MOD3)) %>% as.character()
MOD4_ABS_PATH <- fs::path_norm(file.path(getwd(), LEVEL2_MOD)) %>% as.character()
RUN_LOG_ROWS <- 3L
RUN_LOG_COLS <- 10L
CONFIG_COLS <- 9L
SUM_LOG_COLS <- if (test_bbi_version(read_bbi_path(), .min_version = "3.0.3")) {
23L
} else {
# eigenvalue_issue isn't present yet.
22L
}
ref_json <- jsonlite::fromJSON(system.file("test-refs", "ref_values.json", package = "bbr"))
CONFIG_DATA_PATH_REF <- ref_json$CONFIG_DATA_PATH
CONFIG_DATA_MD5_REF <- ref_json$CONFIG_DATA_MD5
CONFIG_MODEL_MD5_REF <- ref_json$CONFIG_MODEL_MD5
MOD_BBI_VERSION <- ref_json$MOD_BBI_VERSION
MOD1_PARAM_COUNT <- ref_json$MOD1_PARAM_COUNT
MOD1_PARAM_COUNT_FIXED <- ref_json$MOD1_PARAM_COUNT_FIXED
MOD1_OFV_REF <- ref_json$MOD1_OFV_REF
MOD_BBI_VERSION <- ref_json$MOD_BBI_VERSION
MOD_NM_VERSION <- ref_json$MOD_NM_VERSION
# yaml md5 hashes
MOD1_YAML_MD5 <- "6ccf206e167485b5adf29bc135197929"
MOD_LEVEL2_MD5 <- "eb3cada879c886a98d30a18f06f14a68"
ALL_MODS_YAML_MD5 <- c(MOD1_YAML_MD5, rep("56ed64eaa4b500bb6b3f1cc64025aded", 2), "2042dddaefe8bfaa878e713acd7f0eff")
RUN_LOG_YAML_MD5 <- c(MOD1_YAML_MD5, "ac75f5771d66c9b55a1ec68c8789a043", "77525be36ddd665e1508c7ca7541882e")
# model refs
REF_LIST_1 <- list(
model_type = "nonmem",
description = ORIG_DESC,
tags = ORIG_TAGS,
bbi_args = list(
overwrite = TRUE,
threads = 4L),
absolute_model_path = file.path(ABS_MODEL_DIR, "1"),
yaml_md5 = MOD1_YAML_MD5
)
class(REF_LIST_1) <- NM_MOD_CLASS_LIST
REF_LIST_TMP <- list(
model_type = "nonmem",
description = ORIG_DESC,
tags = ORIG_TAGS,
bbi_args = list(
overwrite = TRUE,
threads = 4L),
absolute_model_path = file.path(ABS_MODEL_DIR, "temp"),
yaml_md5 = MOD1_YAML_MD5
)
class(REF_LIST_TMP) <- NM_MOD_CLASS_LIST
#####################
# utils.R constants
#####################
# lists for combining and merging
LIST1 <- list(naw=4, paw=6)
LIST2 <- list(naw=5, saw="hey")
# for combine_directory_path()
ABS_CTL_PATH <- fs::path_norm(file.path(getwd(), MODEL_DIR, glue::glue("{MOD_ID}.ctl"))) %>% as.character()
FAKE_CTL_PATH <- fs::path_norm(file.path(getwd(), MODEL_DIR, CTL_TEST_FILE)) %>% as.character()
########################
# test helper functions
########################
copy_output_dir <- function(orig_mod, new_dir_path) {
fs::dir_copy(
orig_mod[[ABS_MOD_PATH]],
new_dir_path
)
# replace file names with new model ID
orig_mod_id <- get_model_id(orig_mod)
new_mod_id <- basename(new_dir_path)
purrr::walk(fs::dir_ls(new_dir_path), ~ {
if (stringr::str_detect(basename(.x), glue("^{orig_mod_id}"))) {
fs::file_move(
.x,
file.path(
dirname(.x),
stringr::str_replace(
basename(.x),
glue("^{orig_mod_id}"),
new_mod_id
)
)
)
}
})
}
create_all_models <- function() {
mod1 <- read_model(MOD1_PATH)
mod2 <- copy_model_from(mod1, basename(NEW_MOD2), "level 1 copy of 1")
mod3 <- copy_model_from(mod1, basename(NEW_MOD3), "level 1 copy of 1")
fs::dir_create(LEVEL2_DIR)
mod4 <- copy_model_from(mod2, file.path(LEVEL2_SUBDIR, MOD_ID), "level 2 copy of 2")
# load or create models and assign model objects to global environment
assign("mod1", mod1, pos = parent.frame())
assign("mod2", mod2, pos = parent.frame())
assign("mod3", mod3, pos = parent.frame())
assign("mod4", mod4, pos = parent.frame())
ALL_MODS_YAML_MD5 <<- c(system.file("model","nonmem", "basic", "1.yaml", package = "bbr") %>% tools::md5sum(),
system.file("model","nonmem", "basic", "2.yaml", package = "bbr") %>% tools::md5sum(),
system.file("model","nonmem", "basic", "3.yaml", package = "bbr") %>% tools::md5sum(),
system.file("model","nonmem", "basic", "level2", "1.yaml", package = "bbr") %>% tools::md5sum()) %>% unname()
}
copy_all_output_dirs <- function() {
if (!fs::dir_exists(LEVEL2_DIR)) { fs::dir_create(LEVEL2_DIR) }
copy_output_dir(MOD1, NEW_MOD2)
copy_output_dir(MOD1, NEW_MOD3)
copy_output_dir(MOD1, LEVEL2_MOD)
}
create_rlg_models <- function() {
# copy models before creating run log
mod1 <- read_model(MOD1_PATH)
copy_model_from(mod1, basename(NEW_MOD2), .add_tags = NEW_TAGS)
copy_model_from(
mod1,
basename(NEW_MOD3),
.based_on_additional = get_model_id(NEW_MOD2),
.inherit_tags = TRUE,
.update_model_file = FALSE
)
return(invisible(NULL))
}
create_tree_models <- function(
mod1 = read_model(MOD1_PATH),
addl_based_on = FALSE,
multiple_origins = FALSE,
broken_link = FALSE
){
# copy models before creating model tree
mod2 <- copy_model_from(mod1, "2", .add_tags = NEW_TAGS) %>% add_star()
mod3 <- copy_model_from(mod2, "3", .add_tags = "DV: nmol") # test list tag
mod4 <- copy_model_from(mod2, "4")
mod5 <- copy_model_from(mod4, "5")
if(isTRUE(addl_based_on)){
# Multiple based_on
mod6 <- copy_model_from(mod5, "6", .based_on_additional = c("1", "3"),
.star = TRUE, .description = "final model")
mod7 <- copy_model_from(mod6, "7", .based_on_additional = c("2"))
}
if(isTRUE(broken_link) || isTRUE(multiple_origins)){
# broken link models
mod_other <- copy_model_from(mod1, "1000", .overwrite = TRUE)
mod_other2 <- copy_model_from(mod_other, "1001")
mod_other3 <- copy_model_from(mod_other2, "1002")
if(isTRUE(multiple_origins)) remove_based_on(mod_other, "1")
}
if(isTRUE(broken_link)){
# remove starting link by deleting starting model
delete_models(mod_other, .tags = NULL, .force = TRUE) %>% suppressMessages()
}
return(invisible(NULL))
}
clean_test_enviroment <- function(.f = NULL , env = parent.frame())
{
cleanup(env)
if(!is.null(.f)) .f()
withr::defer(cleanup(env), envir = env)
}
cleanup <- function(env = parent.frame()) {
# delete tmp files if they are leftover from previous test
mods_to_kill <- purrr::map_chr(c(seq(2,7), "Parent", "Child", "1000","1001", "1002"), ~ file.path(MODEL_DIR, .x))
for (m in mods_to_kill) {
if (fs::file_exists(yaml_ext(m))) fs::file_delete(yaml_ext(m))
if (fs::file_exists(ctl_ext(m))) fs::file_delete(ctl_ext(m))
if (fs::file_exists(mod_ext(m))) fs::file_delete(mod_ext(m))
}
if (fs::dir_exists(NEW_MOD2)) fs::dir_delete(NEW_MOD2)
if (fs::dir_exists(NEW_MOD3)) fs::dir_delete(NEW_MOD3)
if (fs::dir_exists(BATCH_PARAM_TEST_DIR)) fs::dir_delete(BATCH_PARAM_TEST_DIR)
if (fs::dir_exists(LEVEL2_DIR)) fs::dir_delete(LEVEL2_DIR)
if (file.path(MODEL_DIR, "Parent") %>% dir_exists()) dir_delete(file.path(MODEL_DIR, "Parent"))
if (file.path(MODEL_DIR, "Child") %>% dir_exists()) dir_delete(file.path(MODEL_DIR, "Child"))
# delete model objects from memory
suppressSpecificWarning(rm(mod1, pos = env), .regexpr = "object.+not found")
suppressSpecificWarning(rm(mod2, pos = env), .regexpr = "object.+not found")
suppressSpecificWarning(rm(mod3, pos = env), .regexpr = "object.+not found")
suppressSpecificWarning(rm(mod4, pos = env), .regexpr = "object.+not found")
suppressSpecificWarning(rm(log_df, pos = env),.regexpr = "object.+not found")
}
#' Temporarily perturb a file
#'
#' Appends a line to the end of a text file, and reverts the change in an
#' environment specified by the caller.
#'
#' @param path string giving the file path
#' @param txt string to temporarily append to file
#' @inheritParams withr::defer
perturb_file <- function(path, txt = "foo", envir = parent.frame()) {
checkmate::assert_string(path)
original <- readr::read_file(path)
readr::write_lines(txt, path, append = TRUE)
withr::defer(readr::write_file(original, path), envir)
}
#' Selectively repeat a value
#'
#' It can be useful to create a vector of a particular length with certain
#' elements set to `NA`.
#'
#' @param x The scalar value to repeat.
#' @param i Integer vector of indices that should be `NA`.
#' @param len Non-negative integer giving the desired length of the output
#' vector.
#'
#' @return A vector of length `len` whose elements are either `x` or `NA`.
rep_missing <- function(x, i, len) {
checkmate::assert_scalar(x)
checkmate::assert_integerish(i)
checkmate::assert_count(len)
res <- rep(x, len)
res[i] <- NA
res
}
#' Create a temporary model
#'
#' It is useful to create a model file and YAML file that can be discarded. The
#' files will be deleted when `envir` exits.
#'
#' @param path The path to the YAML file to copy.
#' @param mod_content A string giving the content of the model file.
#' @param mod_ext The extension for the model file.
#' @inheritParams withr::defer
#'
#' @return The absolute model path to the temporary model.
create_temp_model <- function(path = YAML_TEST_FILE,
mod_content = "foo",
mod_ext = "ctl",
envir = parent.frame(),
delete_yaml = TRUE,
delete_mod = TRUE
) {
temp_yaml <- tempfile(fileext = ".yaml")
fs::file_copy(path, temp_yaml)
temp_ctl <- fs::path_ext_set(temp_yaml, mod_ext)
readr::write_file(mod_content, temp_ctl)
to_delete <- character()
if (isTRUE(delete_yaml)) to_delete <- c(to_delete, temp_yaml)
if (isTRUE(delete_mod)) to_delete <- c(to_delete, temp_ctl)
withr::defer(fs::file_delete(to_delete), envir)
# normalizePath() needs to be called when the file actually exists
fs::path_ext_remove(normalizePath(temp_yaml))
}
#' Delete a model object and all of it's accompanying files
cleanup_model <- function(.mod) {
if (fs::file_exists(get_yaml_path(.mod, .check_exists = FALSE))) fs::file_delete(get_yaml_path(.mod))
if (fs::file_exists(get_model_path(.mod, .check_exists = FALSE))) fs::file_delete(get_model_path(.mod))
if (fs::dir_exists(get_output_dir(.mod, .check_exists = FALSE))) fs::dir_delete(get_output_dir(.mod))
if (fs::dir_exists(.mod[[ABS_MOD_PATH]])) fs::dir_delete(.mod[[ABS_MOD_PATH]])
rm(.mod)
}
#' helper to copy a control stream and .ext file to
#' BATCH_PARAM_TEST_DIR for testing
#' @param orig_model_path Path that can load a model with `read_model()`
#' @param new_name Name of new model (not full path)
copy_to_batch_params <- function(orig_model_path, new_name) {
# sanitize new name
checkmate::assert_string(new_name)
new_name <- stringr::str_replace_all(new_name, "[^A-Za-z0-9]", "")
.mod <- read_model(orig_model_path)
new_dir <- file.path(BATCH_PARAM_TEST_DIR, new_name)
if (!fs::dir_exists(new_dir)) fs::dir_create(new_dir)
fs::file_copy(
get_model_path(.mod),
ctl_ext(new_dir)
)
fs::file_copy(
build_path_from_model(.mod, ".ext"),
file.path(new_dir, paste0(new_name, ".ext"))
)
}
with_bg_env <- function(code){
Sys.setenv("BBR_DEV_LOAD_PATH" = "")
# Dont run if inside an R CMD Check environment (package is installed)
if(!testthat::is_checking()){
Sys.setenv("BBR_DEV_LOAD_PATH" = rprojroot::find_rstudio_root_file())
on.exit(Sys.setenv("BBR_DEV_LOAD_PATH" = ""))
}
result <- eval(code)
return(result)
}
# Make sure it's not set when tests are run by default
Sys.setenv("BBR_DEV_LOAD_PATH" = "")
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.