diff --git a/DESCRIPTION b/DESCRIPTION index 957352ff..333bd580 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -21,10 +21,11 @@ Suggests: lifecycle, mockr, rmarkdown, - testthat, + testthat (>= 3.0.0), withr VignetteBuilder: knitr +Config/testthat/edition: 3 Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) diff --git a/R/criterion.R b/R/criterion.R index c468ef21..ff12d913 100644 --- a/R/criterion.R +++ b/R/criterion.R @@ -122,64 +122,3 @@ check_testfun <- function(testfun) { testfun } - -#' @rdname root_criterion -#' @param x An object -#' @export -is_root_criterion <- function(x) { - inherits(x, "root_criterion") -} - -#' @rdname root_criterion -#' @export -as_root_criterion <- function(x) UseMethod("as_root_criterion", x) - -#' @details -#' The `as_root_criterion()` function accepts objects of class -#' `root_criterion`, and character values; the latter will be -#' converted to criteria using `has_file`. -#' -#' @rdname root_criterion -#' @export -as_root_criterion.character <- function(x) { - has_file(x) -} - -#' @rdname root_criterion -#' @export -as_root_criterion.root_criterion <- identity - -#' @export -as_root_criterion.default <- function(x) { - stop("Cannot coerce ", x, " to type root_criterion.", call. = FALSE) -} - -#' @export -format.root_criterion <- function(x, ...) { - if (length(x$desc) > 1) { - c("Root criterion: one of", paste0("- ", x$desc)) - } else { - paste0("Root criterion: ", x$desc) - } -} - -#' @export -print.root_criterion <- function(x, ...) { - cat(format(x), sep = "\n") - invisible(x) -} - -#' @export -#' @rdname root_criterion -#' @details Root criteria can be combined with the `|` operator. The result is a -#' composite root criterion that requires either of the original criteria to -#' match. -#' @param y An object -`|.root_criterion` <- function(x, y) { - stopifnot(is_root_criterion(y)) - - root_criterion( - c(x$testfun, y$testfun), - c(x$desc, y$desc) - ) -} diff --git a/R/has-file.R b/R/has-file.R deleted file mode 100644 index ba2c51ab..00000000 --- a/R/has-file.R +++ /dev/null @@ -1,276 +0,0 @@ -format_lines <- function(n) { - if (n == 1) "line" else paste0(n, " lines") -} - -#' @details -#' The `has_file()` function constructs a criterion that checks for the -#' existence of a specific file (which itself can be in a subdirectory of the -#' root) with specific contents. -#' -#' @rdname root_criterion -#' @param filepath File path (can contain directories) -#' @param contents Regular expression to match the file contents -#' @inheritParams base::readLines -#' @export -has_file <- function(filepath, contents = NULL, n = -1L) { - force(filepath) - force(contents) - force(n) - - testfun <- eval(bquote(function(path) { - testfile <- file.path(path, .(filepath)) - if (!file.exists(testfile)) { - return(FALSE) - } - if (dir.exists(testfile)) { - return(FALSE) - } - match_contents(testfile, .(contents), .(n)) - })) - - desc <- paste0( - "contains a file `", filepath, "`", - if (!is.null(contents)) { - paste0( - " with contents matching `", contents, "`", - if (n >= 0L) paste0(" in the first ", format_lines(n)) - ) - } - ) - - root_criterion(testfun, desc) -} - -#' @details -#' The `has_dir()` function constructs a criterion that checks for the -#' existence of a specific directory. -#' -#' @rdname root_criterion -#' @export -has_dir <- function(filepath) { - force(filepath) - - testfun <- eval(bquote(function(path) { - testfile <- file.path(path, .(filepath)) - dir.exists(testfile) - })) - - desc <- paste0("contains a directory `", filepath, "`") - - root_criterion(testfun, desc) -} - -#' @details -#' The `has_file_pattern()` function constructs a criterion that checks for the -#' existence of a file that matches a pattern, with specific contents. -#' -#' @rdname root_criterion -#' @param pattern Regular expression to match the file name -#' @inheritParams base::readLines -#' @export -has_file_pattern <- function(pattern, contents = NULL, n = -1L) { - force(pattern) - force(contents) - force(n) - - testfun <- eval(bquote(function(path) { - files <- list_files(path, .(pattern)) - for (f in files) { - if (!match_contents(f, .(contents), .(n))) { - next - } - return(TRUE) - } - return(FALSE) - })) - - desc <- paste0( - "contains a file matching `", pattern, "`", - if (!is.null(contents)) { - paste0( - " with contents matching `", contents, "`", - if (n >= 0L) paste0(" in the first ", format_lines(n)) - ) - } - ) - - root_criterion(testfun, desc) -} - -#' @details -#' The `has_basename()` function constructs a criterion that checks if the -#' [base::basename()] of the root directory has a specific name, -#' with support for case-insensitive file systems. -#' -#' @rdname root_criterion -#' @param basename A directory name, without subdirectories -#' @export -has_basename <- function(basename, subdir = NULL) { - force(basename) - - testfun <- eval(bquote(function(path) { - # Support case insensitive file systems. - tolower(basename(path)) == tolower(.(basename)) && dir.exists(file.path(dirname(path), .(basename))) - })) - - desc <- paste0("directory name is `", basename, "`") - - root_criterion(testfun, desc, subdir = subdir) -} - -#' @export -is_rstudio_project <- has_file_pattern("[.]Rproj$", contents = "^Version: ", n = 1L) - -#' @export -is_r_package <- has_file("DESCRIPTION", contents = "^Package: ") - -#' @export -is_remake_project <- has_file("remake.yml") - -#' @export -is_drake_project <- has_dir(".drake") - -#' @export -is_projectile_project <- has_file(".projectile") - -#' @export -is_git_root <- has_dir(".git") | has_file(".git", contents = "^gitdir: ") - -#' @export -is_svn_root <- has_dir(".svn") - -#' @export -is_vcs_root <- is_git_root | is_svn_root - -#' @export -is_testthat <- has_basename("testthat", c("tests/testthat", "testthat")) - -#' @export -from_wd <- root_criterion(function(path) TRUE, "from current working directory") - -#' Prespecified criteria -#' -#' This is a collection of commonly used root criteria. -#' -#' @format NULL -#' -#' @export -criteria <- structure( - list( - is_rstudio_project = is_rstudio_project, - is_r_package = is_r_package, - is_remake_project = is_remake_project, - is_projectile_project = is_projectile_project, - is_git_root = is_git_root, - is_svn_root = is_svn_root, - is_vcs_root = is_vcs_root, - is_testthat = is_testthat, - from_wd = from_wd - ), - class = "root_criteria" -) - -#' @export -#' @importFrom utils str -str.root_criteria <- function(object, ...) { - str(lapply(object, format)) -} - -#' @details -#' `is_rstudio_project` looks for a file with extension `.Rproj`. -#' -#' @format NULL -#' @rdname criteria -#' @export -"is_rstudio_project" - -#' @details -#' `is_r_package` looks for a `DESCRIPTION` file. -#' -#' @format NULL -#' @rdname criteria -#' @export -"is_r_package" - -#' @details -#' `is_remake_project` looks for a `remake.yml` file. -#' -#' @format NULL -#' @rdname criteria -#' @export -"is_remake_project" - -#' @details -#' `is_drake_project` looks for a `.drake` directory. -#' -#' @format NULL -#' @rdname criteria -#' @export -"is_drake_project" - -#' @details -#' `is_projectile_project` looks for a `.projectile` file. -#' -#' @format NULL -#' @rdname criteria -#' @export -"is_projectile_project" - -#' @details -#' `is_git_root` looks for a `.git` directory. -#' -#' @format NULL -#' @rdname criteria -#' @export -"is_git_root" - -#' @details -#' `is_svn_root` looks for a `.svn` directory. -#' -#' @format NULL -#' @rdname criteria -#' @export -"is_svn_root" - -#' @details -#' `is_vcs_root` looks for the root of a version control -#' system, currently only Git and SVN are supported. -#' -#' @format NULL -#' @rdname criteria -#' @export -"is_vcs_root" - -#' @details -#' `is_testthat` looks for the `testthat` directory, works when -#' developing, testing, and checking a package. -#' -#' @format NULL -#' @rdname criteria -#' @export -"is_testthat" - -#' @details -#' `from_wd` uses the current working directory. -#' -#' @format NULL -#' @rdname criteria -#' @export -"from_wd" - - -list_files <- function(path, filename) { - files <- dir(path = path, pattern = filename, all.files = TRUE, full.names = TRUE) - dirs <- dir.exists(files) - files <- files[!dirs] - files -} - -match_contents <- function(f, contents, n) { - if (is.null(contents)) { - return(TRUE) - } - - fc <- readLines(f, n) - any(grepl(contents, fc)) -} diff --git a/R/path.R b/R/path.R index be00db3b..70a0a6d2 100644 --- a/R/path.R +++ b/R/path.R @@ -13,11 +13,14 @@ path <- function(...) { } # Side effect: check recycling rules - components <- as.data.frame(dots, stringsAsFactors = FALSE) + component_df <- as.data.frame(dots, stringsAsFactors = FALSE) - missing <- apply(is.na(components), 1, any) + missing <- apply(is.na(component_df), 1, any) + + components <- lapply(component_df, function(x) enc2utf8(as.character(x))) out <- do.call(file.path, components) out[missing] <- NA_character_ + Encoding(out) <- "UTF-8" out } diff --git a/R/root.R b/R/root.R index 036a5db0..bf3744df 100644 --- a/R/root.R +++ b/R/root.R @@ -1,3 +1,64 @@ +#' @rdname root_criterion +#' @param x An object +#' @export +is_root_criterion <- function(x) { + inherits(x, "root_criterion") +} + +#' @rdname root_criterion +#' @export +as_root_criterion <- function(x) UseMethod("as_root_criterion", x) + +#' @details +#' The `as_root_criterion()` function accepts objects of class +#' `root_criterion`, and character values; the latter will be +#' converted to criteria using `has_file`. +#' +#' @rdname root_criterion +#' @export +as_root_criterion.character <- function(x) { + has_file(x) +} + +#' @rdname root_criterion +#' @export +as_root_criterion.root_criterion <- identity + +#' @export +as_root_criterion.default <- function(x) { + stop("Cannot coerce ", x, " to type root_criterion.", call. = FALSE) +} + +#' @export +format.root_criterion <- function(x, ...) { + if (length(x$desc) > 1) { + c("Root criterion: one of", paste0("- ", x$desc)) + } else { + paste0("Root criterion: ", x$desc) + } +} + +#' @export +print.root_criterion <- function(x, ...) { + cat(format(x), sep = "\n") + invisible(x) +} + +#' @export +#' @rdname root_criterion +#' @details Root criteria can be combined with the `|` operator. The result is a +#' composite root criterion that requires either of the original criteria to +#' match. +#' @param y An object +`|.root_criterion` <- function(x, y) { + stopifnot(is_root_criterion(y)) + + root_criterion( + c(x$testfun, y$testfun), + c(x$desc, y$desc) + ) +} + #' Find the root of a directory hierarchy #' #' A \emph{root} is defined as a directory that contains a regular file @@ -89,3 +150,264 @@ get_root_desc <- function(criterion, path) { call. = FALSE ) } + + +format_lines <- function(n) { + if (n == 1) "line" else paste0(n, " lines") +} + +#' @details +#' The `has_file()` function constructs a criterion that checks for the +#' existence of a specific file (which itself can be in a subdirectory of the +#' root) with specific contents. +#' +#' @rdname root_criterion +#' @param filepath File path (can contain directories) +#' @param contents Regular expression to match the file contents +#' @inheritParams base::readLines +#' @export +has_file <- function(filepath, contents = NULL, n = -1L) { + force(filepath) + force(contents) + force(n) + + testfun <- eval(bquote(function(path) { + testfile <- file.path(path, .(filepath)) + if (!file.exists(testfile)) { + return(FALSE) + } + if (dir.exists(testfile)) { + return(FALSE) + } + match_contents(testfile, .(contents), .(n)) + })) + + desc <- paste0( + "contains a file `", filepath, "`", + if (!is.null(contents)) { + paste0( + " with contents matching `", contents, "`", + if (n >= 0L) paste0(" in the first ", format_lines(n)) + ) + } + ) + + root_criterion(testfun, desc) +} + +#' @details +#' The `has_dir()` function constructs a criterion that checks for the +#' existence of a specific directory. +#' +#' @rdname root_criterion +#' @export +has_dir <- function(filepath) { + force(filepath) + + testfun <- eval(bquote(function(path) { + testfile <- file.path(path, .(filepath)) + dir.exists(testfile) + })) + + desc <- paste0("contains a directory `", filepath, "`") + + root_criterion(testfun, desc) +} + +#' @details +#' The `has_file_pattern()` function constructs a criterion that checks for the +#' existence of a file that matches a pattern, with specific contents. +#' +#' @rdname root_criterion +#' @param pattern Regular expression to match the file name +#' @inheritParams base::readLines +#' @export +has_file_pattern <- function(pattern, contents = NULL, n = -1L) { + force(pattern) + force(contents) + force(n) + + testfun <- eval(bquote(function(path) { + files <- list_files(path, .(pattern)) + for (f in files) { + if (!match_contents(f, .(contents), .(n))) { + next + } + return(TRUE) + } + return(FALSE) + })) + + desc <- paste0( + "contains a file matching `", pattern, "`", + if (!is.null(contents)) { + paste0( + " with contents matching `", contents, "`", + if (n >= 0L) paste0(" in the first ", format_lines(n)) + ) + } + ) + + root_criterion(testfun, desc) +} + +#' @details +#' The `has_basename()` function constructs a criterion that checks if the +#' [base::basename()] of the root directory has a specific name, +#' with support for case-insensitive file systems. +#' +#' @rdname root_criterion +#' @param basename A directory name, without subdirectories +#' @export +has_basename <- function(basename, subdir = NULL) { + force(basename) + + testfun <- eval(bquote(function(path) { + # Support case insensitive file systems. + tolower(basename(path)) == tolower(.(basename)) && dir.exists(file.path(dirname(path), .(basename))) + })) + + desc <- paste0("directory name is `", basename, "`") + + root_criterion(testfun, desc, subdir = subdir) +} + +#' @export +is_rstudio_project <- has_file_pattern("[.]Rproj$", contents = "^Version: ", n = 1L) + +#' @export +is_r_package <- has_file("DESCRIPTION", contents = "^Package: ") + +#' @export +is_remake_project <- has_file("remake.yml") + +#' @export +is_drake_project <- has_dir(".drake") + +#' @export +is_projectile_project <- has_file(".projectile") + +#' @export +is_git_root <- has_dir(".git") | has_file(".git", contents = "^gitdir: ") + +#' @export +is_svn_root <- has_dir(".svn") + +#' @export +is_vcs_root <- is_git_root | is_svn_root + +#' @export +is_testthat <- has_basename("testthat", c("tests/testthat", "testthat")) + +#' @export +from_wd <- root_criterion(function(path) TRUE, "from current working directory") + +#' Prespecified criteria +#' +#' This is a collection of commonly used root criteria. +#' +#' @format NULL +#' +#' @export +criteria <- structure( + list( + is_rstudio_project = is_rstudio_project, + is_r_package = is_r_package, + is_remake_project = is_remake_project, + is_projectile_project = is_projectile_project, + is_git_root = is_git_root, + is_svn_root = is_svn_root, + is_vcs_root = is_vcs_root, + is_testthat = is_testthat, + from_wd = from_wd + ), + class = "root_criteria" +) + +#' @export +#' @importFrom utils str +str.root_criteria <- function(object, ...) { + str(lapply(object, format)) +} + +#' @details +#' `is_rstudio_project` looks for a file with extension `.Rproj`. +#' +#' @format NULL +#' @rdname criteria +#' @export +"is_rstudio_project" + +#' @details +#' `is_r_package` looks for a `DESCRIPTION` file. +#' +#' @format NULL +#' @rdname criteria +#' @export +"is_r_package" + +#' @details +#' `is_remake_project` looks for a `remake.yml` file. +#' +#' @format NULL +#' @rdname criteria +#' @export +"is_remake_project" + +#' @details +#' `is_drake_project` looks for a `.drake` directory. +#' +#' @format NULL +#' @rdname criteria +#' @export +"is_drake_project" + +#' @details +#' `is_projectile_project` looks for a `.projectile` file. +#' +#' @format NULL +#' @rdname criteria +#' @export +"is_projectile_project" + +#' @details +#' `is_git_root` looks for a `.git` directory. +#' +#' @format NULL +#' @rdname criteria +#' @export +"is_git_root" + +#' @details +#' `is_svn_root` looks for a `.svn` directory. +#' +#' @format NULL +#' @rdname criteria +#' @export +"is_svn_root" + +#' @details +#' `is_vcs_root` looks for the root of a version control +#' system, currently only Git and SVN are supported. +#' +#' @format NULL +#' @rdname criteria +#' @export +"is_vcs_root" + +#' @details +#' `is_testthat` looks for the `testthat` directory, works when +#' developing, testing, and checking a package. +#' +#' @format NULL +#' @rdname criteria +#' @export +"is_testthat" + +#' @details +#' `from_wd` uses the current working directory. +#' +#' @format NULL +#' @rdname criteria +#' @export +"from_wd" diff --git a/R/thisfile.R b/R/thisfile.R index 9945acdd..1e35bb1a 100644 --- a/R/thisfile.R +++ b/R/thisfile.R @@ -1,3 +1,4 @@ +# nocov start #' Determines the path of the currently running script #' #' @description @@ -146,3 +147,4 @@ thisfile_knit <- function() { NULL } +# nocov end diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 00000000..0fafe692 --- /dev/null +++ b/R/utils.R @@ -0,0 +1,15 @@ +list_files <- function(path, filename) { + files <- dir(path = path, pattern = filename, all.files = TRUE, full.names = TRUE) + dirs <- dir.exists(files) + files <- files[!dirs] + files +} + +match_contents <- function(f, contents, n) { + if (is.null(contents)) { + return(TRUE) + } + + fc <- readLines(f, n) + any(grepl(contents, fc)) +} diff --git a/man/criteria.Rd b/man/criteria.Rd index 8488ca27..cbcba8ab 100644 --- a/man/criteria.Rd +++ b/man/criteria.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/has-file.R +% Please edit documentation in R/root.R \docType{data} \name{criteria} \alias{criteria} diff --git a/man/root_criterion.Rd b/man/root_criterion.Rd index c7c07f8c..d36d75bc 100644 --- a/man/root_criterion.Rd +++ b/man/root_criterion.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/criterion.R, R/has-file.R +% Please edit documentation in R/criterion.R, R/root.R \name{root_criterion} \alias{root_criterion} \alias{is_root_criterion} diff --git a/tests/testthat/_snaps/root.md b/tests/testthat/_snaps/root.md new file mode 100644 index 00000000..d91ae7bb --- /dev/null +++ b/tests/testthat/_snaps/root.md @@ -0,0 +1,89 @@ +# Formatting + + Code + format(is_r_package) + Output + [1] "Root criterion: contains a file `DESCRIPTION` with contents matching `^Package: `" + +--- + + Code + is_r_package + Output + Root criterion: contains a file `DESCRIPTION` with contents matching `^Package: ` + +--- + + Code + is_vcs_root + Output + Root criterion: one of + - contains a directory `.git` + - contains a file `.git` with contents matching `^gitdir: ` + - contains a directory `.svn` + +--- + + Code + criteria + Output + $is_rstudio_project + Root criterion: contains a file matching `[.]Rproj$` with contents matching `^Version: ` in the first line + + $is_r_package + Root criterion: contains a file `DESCRIPTION` with contents matching `^Package: ` + + $is_remake_project + Root criterion: contains a file `remake.yml` + + $is_projectile_project + Root criterion: contains a file `.projectile` + + $is_git_root + Root criterion: one of + - contains a directory `.git` + - contains a file `.git` with contents matching `^gitdir: ` + + $is_svn_root + Root criterion: contains a directory `.svn` + + $is_vcs_root + Root criterion: one of + - contains a directory `.git` + - contains a file `.git` with contents matching `^gitdir: ` + - contains a directory `.svn` + + $is_testthat + Root criterion: directory name is `testthat` (also look in subdirectories: `tests/testthat`, `testthat`) + + $from_wd + Root criterion: from current working directory + + attr(,"class") + [1] "root_criteria" + +--- + + Code + str(criteria) + Output + List of 9 + $ is_rstudio_project : chr "Root criterion: contains a file matching `[.]Rproj$` with contents matching `^Version: ` in the first line" + $ is_r_package : chr "Root criterion: contains a file `DESCRIPTION` with contents matching `^Package: `" + $ is_remake_project : chr "Root criterion: contains a file `remake.yml`" + $ is_projectile_project: chr "Root criterion: contains a file `.projectile`" + $ is_git_root : chr [1:3] "Root criterion: one of" "- contains a directory `.git`" "- contains a file `.git` with contents matching `^gitdir: `" + $ is_svn_root : chr "Root criterion: contains a directory `.svn`" + $ is_vcs_root : chr [1:4] "Root criterion: one of" "- contains a directory `.git`" "- contains a file `.git` with contents matching `^gitdir: `" "- contains a directory `.svn`" + $ is_testthat : chr "Root criterion: directory name is `testthat` (also look in subdirectories: `tests/testthat`, `testthat`)" + $ from_wd : chr "Root criterion: from current working directory" + +# Combining criteria + + Code + comb_crit + Output + Root criterion: one of + - contains a file `DESCRIPTION` with contents matching `^Package: ` + - contains a file matching `[.]Rproj$` with contents matching `^Version: ` in the first line + diff --git a/tests/testthat/_snaps/testthat.md b/tests/testthat/_snaps/testthat.md new file mode 100644 index 00000000..0060083c --- /dev/null +++ b/tests/testthat/_snaps/testthat.md @@ -0,0 +1,7 @@ +# is_testthat + + Code + is_testthat + Output + Root criterion: directory name is `testthat` (also look in subdirectories: `tests/testthat`, `testthat`) + diff --git a/tests/testthat/test-criterion.R b/tests/testthat/test-criterion.R index 4ab70800..afbc2f40 100644 --- a/tests/testthat/test-criterion.R +++ b/tests/testthat/test-criterion.R @@ -1,4 +1,24 @@ -context("criterion") +test_that("Shortcuts", { + expect_equal( + make_find_root_file("testthat.R")("testthat"), + normalizePath(getwd(), winslash = "/") + ) + + R <- make_fix_root_file("testthat.R", getwd()) + + oldwd <- withr::local_dir("~") + + expect_equal( + normalizePath(R("testthat"), mustWork = TRUE), + normalizePath(oldwd, mustWork = TRUE) + ) + + path <- R() + expect_equal( + normalizePath(R(path, "testthat"), mustWork = TRUE), + normalizePath(oldwd, mustWork = TRUE) + ) +}) test_that("root_criterion", { expect_error( @@ -9,48 +29,9 @@ test_that("root_criterion", { expect_true(is_root_criterion(root_criterion(function(path) FALSE, "Never"))) }) -test_that("is_root_criterion", { - expect_true(is_root_criterion(has_file("DESCRIPTION"))) - expect_false(is_root_criterion("DESCRIPTION")) - expect_true(is_root_criterion(as_root_criterion("DESCRIPTION"))) - expect_equal(as_root_criterion("x"), has_file("x")) - expect_error(as_root_criterion(5), "Cannot coerce") -}) - test_that("Absolute paths are returned", { expect_equal( find_root("testthat.R"), normalizePath(find_root("testthat.R"), winslash = "/") ) }) - -test_that("Formatting", { - expect_match( - paste(format(is_r_package), collapse = "\n"), - "^Root criterion: .*DESCRIPTION" - ) - expect_output(print(is_r_package), "^Root criterion: .*DESCRIPTION") - expect_output(print(is_vcs_root), "^Root criterion: one of\n- .*[.]git.*\n- .*[.]svn") -}) - -test_that("Formatting criteria", { - expect_output( - str(criteria), - "^List of " - ) -}) - -test_that("Combining criteria", { - skip_on_cran() - - comb_crit <- is_r_package | is_rstudio_project - - expect_true(is_root_criterion(comb_crit)) - - expect_match(paste0(format(comb_crit), collapse = "\n"), "\n- .*\n- ") - - expect_equal( - find_root(comb_crit, "hierarchy"), - find_root(is_rstudio_project, "hierarchy/a") - ) -}) diff --git a/tests/testthat/test-file.R b/tests/testthat/test-file.R new file mode 100644 index 00000000..4020ff36 --- /dev/null +++ b/tests/testthat/test-file.R @@ -0,0 +1,40 @@ +test_that("has_file", { + wd <- normalizePath(getwd(), winslash = "/") + hierarchy <- function(n = 0L) { + do.call(file.path, list(wd, "hierarchy", "a", "b", "c")[seq_len(n + 1L)]) + } + + stop_path <- hierarchy(1L) + path <- hierarchy(4L) + + mockr::with_mock( + is_root = function(x) x == stop_path, + expect_equal( + find_root_file("c", criterion = "b/a", path = path), + file.path(hierarchy(2L), "c") + ), + # Absolute paths are stripped + expect_equal( + find_root_file("/x", "y", criterion = "b/a", path = path), + file.path("/x", "y") + ), + expect_identical( + find_root_file("c", NA, criterion = "b/a", path = path), + NA_character_ + ), + expect_identical( + find_root_file("c", character(), criterion = "b/a", path = path), + character() + ), + expect_error( + find_root_file(letters[1:2], letters[1:3], criterion = "a", path = path) + ), + expect_error( + find_root_file(letters[1:2], character(), criterion = "a", path = path) + ), + expect_error( + find_root_file(c("b", "/x"), "c", criterion = "a", path = path), + "absolute and relative" + ) + ) +}) diff --git a/tests/testthat/test-make.R b/tests/testthat/test-make.R deleted file mode 100644 index b043c45f..00000000 --- a/tests/testthat/test-make.R +++ /dev/null @@ -1,23 +0,0 @@ -context("make") - -test_that("Shortcuts", { - expect_equal( - make_find_root_file("testthat.R")("testthat"), - normalizePath(getwd(), winslash = "/") - ) - - R <- make_fix_root_file("testthat.R", getwd()) - - oldwd <- withr::local_dir("~") - - expect_equal( - normalizePath(R("testthat"), mustWork = TRUE), - normalizePath(oldwd, mustWork = TRUE) - ) - - path <- R() - expect_equal( - normalizePath(R(path, "testthat"), mustWork = TRUE), - normalizePath(oldwd, mustWork = TRUE) - ) -}) diff --git a/tests/testthat/test-path.R b/tests/testthat/test-path.R new file mode 100644 index 00000000..a9ca8ac0 --- /dev/null +++ b/tests/testthat/test-path.R @@ -0,0 +1,45 @@ +# Adapted from fs +describe("path", { + it("returns paths UTF-8 encoded", { + skip_on_os("solaris") + expect_equal(Encoding(path("föö")), "UTF-8") + }) + + it("returns paths UTF-8 encoded 2", { + skip_on_os("solaris") + skip_on_os("windows") + expect_equal(Encoding(path("\U4F60\U597D.R")), "UTF-8") + }) + + it("returns empty strings for empty inputs", { + expect_equal(path(""), "") + expect_equal(path(character()), character()) + expect_equal(path("foo", character(), "bar"), character()) + }) + + it("propagates NA strings", { + expect_equal(path(NA_character_), NA_character_) + expect_equal(path("foo", NA_character_), NA_character_) + expect_equal(path(c("foo", "bar"), c("baz", NA_character_)), c("foo/baz", NA_character_)) + }) + + it("does not double paths", { + expect_equal(path("", "foo"), "/foo") + + # This could be a UNC path, so we keep the doubled path. + expect_equal(path("//foo", "bar"), "//foo/bar") + }) + + it("errors on paths which are too long", { + expect_error(path(paste(rep("a", 100000), collapse = ""))) + }) + + it("follows recycling rules", { + expect_equal(path("foo", character()), character()) + expect_equal(path("foo", "bar"), "foo/bar") + expect_equal(path("foo", c("bar", "baz")), c("foo/bar", "foo/baz")) + expect_equal(path(c("foo", "qux"), c("bar", "baz")), c("foo/bar", "qux/baz")) + + expect_error(path(c("foo", "qux", "foo2"), c("bar", "baz"))) + }) +}) diff --git a/tests/testthat/test-root.R b/tests/testthat/test-root.R index 3fc3923e..a0daa47e 100644 --- a/tests/testthat/test-root.R +++ b/tests/testthat/test-root.R @@ -1,4 +1,47 @@ -context("root") + +test_that("is_root_criterion", { + expect_true(is_root_criterion(has_file("DESCRIPTION"))) + expect_false(is_root_criterion("DESCRIPTION")) + expect_true(is_root_criterion(as_root_criterion("DESCRIPTION"))) +}) + +test_that("as_root_criterion", { + reset_env <- function(x) { + if (is.function(x)) { + environment(x) <- .GlobalEnv + } else if (is.list(x)) { + x <- lapply(x, reset_env) + } + x + } + + expect_equal( + lapply(as_root_criterion("x"), reset_env), + lapply(has_file("x"), reset_env) + ) + expect_error(as_root_criterion(5), "Cannot coerce") +}) + +test_that("Formatting", { + expect_snapshot(format(is_r_package)) + expect_snapshot(is_r_package) + expect_snapshot(is_vcs_root) + expect_snapshot(criteria) + expect_snapshot(str(criteria)) +}) + +test_that("Combining criteria", { + comb_crit <- is_r_package | is_rstudio_project + + expect_true(is_root_criterion(comb_crit)) + + expect_snapshot(comb_crit) + + expect_equal( + find_root(comb_crit, "hierarchy"), + find_root(is_rstudio_project, "hierarchy/a") + ) +}) test_that("has_file", { wd <- normalizePath(getwd(), winslash = "/") @@ -14,22 +57,6 @@ test_that("has_file", { expect_equal(find_root("a", path = path), hierarchy(3L)), expect_equal(find_root("b", path = path), hierarchy(3L)), expect_equal(find_root("b/a", path = path), hierarchy(2L)), - expect_equal( - find_root_file("c", criterion = "b/a", path = path), - file.path(hierarchy(2L), "c") - ), - expect_equal( - find_root_file("/x", "y", criterion = "b/a", path = path), - file.path("/x", "y") - ), - expect_identical( - find_root_file("c", NA, criterion = "b/a", path = path), - NA_character_ - ), - expect_identical( - find_root_file("c", character(), criterion = "b/a", path = path), - character() - ), expect_equal(find_root("c", path = path), hierarchy(1L)), expect_equal(find_root("d", path = path), hierarchy(4L)), expect_equal(find_root(has_file("DESCRIPTION", "^Package: ", 1), path = path), hierarchy(1L)), @@ -49,16 +76,6 @@ test_that("has_file", { expect_error( find_root(has_file("e", "f", 1), path = path), "No root directory found.* file `.*` with contents .* in the first line" - ), - expect_error( - find_root_file(letters[1:2], letters[1:3], criterion = "a", path = path) - ), - expect_error( - find_root_file(letters[1:2], character(), criterion = "a", path = path) - ), - expect_error( - find_root_file(c("b", "/x"), "c", criterion = "a", path = path), - "absolute and relative" ) ) }) @@ -120,15 +137,6 @@ test_that("has_dir", { is_root = function(x) x == stop_path, expect_equal(find_root(has_dir("a"), path = path), hierarchy(1L)), expect_equal(find_root(has_dir("b"), path = path), hierarchy(2L)), - expect_equal( - find_root_file("c", criterion = has_dir("b"), path = path), - file.path(hierarchy(2L), "c") - ), - # Absolute paths are stripped - expect_equal( - find_root_file(hierarchy(3L), "c", criterion = has_dir("b"), path = path), - hierarchy(4L) - ), expect_equal(find_root(has_dir("c"), path = path), hierarchy(3L)), expect_error( find_root(has_dir("e"), path = path), @@ -155,10 +163,6 @@ test_that("has_basename", { is_root = function(x) x == stop_path, expect_equal(find_root(has_basename("a"), path = path), hierarchy(2L)), expect_equal(find_root(has_basename("b"), path = path), hierarchy(3L)), - expect_equal( - find_root_file("c", criterion = has_basename("b"), path = path), - file.path(hierarchy(3L), "c") - ), expect_equal(find_root(has_basename("c"), path = path), hierarchy(4L)), expect_error( find_root(has_basename("d"), path = path), diff --git a/tests/testthat/test-testthat.R b/tests/testthat/test-testthat.R index ff488a3c..8a348476 100644 --- a/tests/testthat/test-testthat.R +++ b/tests/testthat/test-testthat.R @@ -1,14 +1,5 @@ -context("testthat") - test_that("is_testthat", { - expect_match( - paste(format(is_testthat), collapse = "\n"), - "^.*directory name is `testthat` .* subdirectories.*`tests/testthat`.*`testthat`.*$" - ) - expect_match( - paste(format(is_testthat), collapse = "\n"), - "directory name is `testthat` .* subdirectories.*`tests/testthat`.*`testthat`.*" - ) + expect_snapshot(is_testthat) testthat_path <- normalizePath("package/tests/testthat", winslash = "/") expect_equal(is_testthat$find_file(path = "package"), testthat_path) diff --git a/tests/testthat/test-thisfile.R b/tests/testthat/test-thisfile.R deleted file mode 100644 index f8956ee9..00000000 --- a/tests/testthat/test-thisfile.R +++ /dev/null @@ -1,72 +0,0 @@ -context("thisfile") - -test_that("thisfile works with source", { - skip_on_cran() - res <- source("scripts/thisfile.R") - expect_true(grepl("thisfile.R$", res$value)) -}) - -test_that("thisfile works with Rscript", { - skip_on_cran() - skip_on_os("windows") - rscript_path <- shQuote(file.path(R.home("bin"), "Rscript"), if (.Platform$OS.type == "windows") "cmd" else "sh") - p <- pipe(paste0(rscript_path, " scripts/thisfile-cat.R")) - on.exit(close(p)) - res <- readLines(p) - expect_equal("scripts/thisfile-cat.R", res[[length(res)]]) -}) - -test_that("thisfile works with R", { - skip_on_cran() - skip_on_os("windows") - r_path <- shQuote(file.path(R.home("bin"), "R"), if (.Platform$OS.type == "windows") "cmd" else "sh") - p <- pipe(paste0(r_path, " --slave --vanilla --no-save -f scripts/thisfile-cat.R")) - on.exit(close(p)) - res <- readLines(p) - expect_equal("scripts/thisfile-cat.R", res[[length(res)]]) -}) - -test_that("thisfile works with knitr", { - skip_if_not_installed("knitr") - out <- tempfile(pattern = "rprojroot", fileext = ".md") - expect_message( - knitr::knit("scripts/thisfile.Rmd", output = out, quiet = TRUE), - normalizePath("scripts/thisfile.Rmd"), - fixed = TRUE - ) -}) - -test_that("thisfile works with rmarkdown", { - skip_if_not_installed("rmarkdown") - skip_if_not(rmarkdown::pandoc_available()) - - out <- tempfile(pattern = "rprojroot", fileext = ".md") - expect_message( - rmarkdown::render( - "scripts/thisfile.Rmd", - output_file = out, - output_format = "md_document", quiet = TRUE - ), - normalizePath("scripts/thisfile.Rmd"), - fixed = TRUE - ) -}) - -test_that("thisfile works with spin", { - skip("TODO") - out <- tempfile(pattern = "rprojroot", fileext = ".md") - knitr::spin("scripts/thisfile-cat.R", format = "Rmd", precious = TRUE) - res <- readLines(out) - expect_equal(normalizePath("scripts/thisfile.Rmd"), normalizePath(res)) -}) - -test_that("thisfile works with rendering an R script", { - skip("TODO") - out <- tempfile(pattern = "rprojroot", fileext = ".md") - rmarkdown::render("scripts/thisfile-cat.R", - output_file = out, - output_format = "md_document", quiet = TRUE - ) - res <- readLines(out) - expect_equal(normalizePath("scripts/thisfile.Rmd"), normalizePath(res)) -})