Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recursive test_r() #433

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: rhino
Title: A Framework for Enterprise Shiny Applications
Version: 1.3.0
Version: 1.3.0.8002
Authors@R:
c(
person("Kamil", "Żyła", role = c("aut", "cre"), email = "opensource+kamil@appsilon.com"),
Expand Down Expand Up @@ -40,6 +40,7 @@ Imports:
yaml
Suggests:
covr,
crayon,
knitr,
mockery,
rcmdcheck,
Expand Down
156 changes: 156 additions & 0 deletions R/recursive_unit_tests.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
RecursiveUnitTests <- R6::R6Class("RecursiveUnitTests", # nolint
public = list(
initialize = function(path, filter = "test-.+\\.R$", recursive = TRUE) {
if (length(path) > 1) {
cli::cli_abort("Please provide a single path.")
}
private$path <- path
private$filter <- filter
private$recursive <- recursive

private$get_valid_test_paths()
},
run_tests = function(...) {
if (private$is_single_test_file()) {
testthat::test_file(path = private$valid_paths, ...)
} else if (private$is_single_test_dir()) {
testthat::test_dir(path = private$valid_paths, ...)
} else if (private$is_multiple_test_dirs()) {
private$test_recursive(...)
} else {
cli::cli_abort("Test run failed!")
}
}
),
private = list(
filter = NULL,
path = NULL,
valid_paths = NULL,
recursive = TRUE,
get_valid_test_paths = function() {
if (fs::is_file(private$path)) {
private$valid_paths <- fs::path_filter(private$path, regexp = private$filter)
}

if (fs::is_dir(private$path)) {
valid_paths <- unique(
fs::path_dir(
fs::dir_ls(path = private$path,
regexp = private$filter,
recurse = private$recursive, type = "file")
)
)

private$valid_paths <- valid_paths[order(valid_paths)]
Copy link
Contributor Author

@radbasa radbasa Jan 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because testthat::test_dir() throws an error if given a path without a valid test file, we get a list of valid test files, then get their paths.

}

if (length(private$valid_paths) == 0) {
cli::cli_abort("No valid test file/s found in {.var {private$path}}.",
call = rlang::caller_env(n = 3))
}
},
is_single_test_file = function() {
length(private$valid_paths) == 1 && fs::is_file(private$valid_paths)
},
is_single_test_dir = function() {
length(private$valid_paths) == 1
},
is_multiple_test_dirs = function() {
length(private$valid_paths) > 1
},
run_recursive_test_dir = function(...) {
t(
sapply(private$valid_paths, function(this_path) {
private$cat_cr()
cli::cat_line("Test Directory: ", this_path)

single_test_result <- as.data.frame(
testthat::test_dir(path = this_path, stop_on_failure = FALSE, ...))

colSums(single_test_result[, c("failed", "warning", "skipped", "passed")])
})
)
},
show_final_line = function(test_results) {
final_line_results <- colSums(test_results)

cli::cat_line(
summary_line(final_line_results[["failed"]],
final_line_results[["warning"]],
final_line_results[["skipped"]],
final_line_results[["passed"]])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation: Use the following indentation style when arguments don't fit on one line:

func(
  long_arg_1, # All arguments indented with 2 spaces.
  long_arg_2
) # Closing parenthesis on new line, with the same indentation as `func`.

This way if func is renamed there is no need to adjust the indentation of its arguments, which is easy to forget about and leads to larger / not meaningful diffs (harder to review).

)

private$cat_cr()
},
show_summary = function(test_results) {
private$cat_cr()
cli::cat_rule(cli::style_bold("Rhino App Summary"), line = 2)
private$cat_cr()

cli::cat_line(
colourise(cli::symbol$tick, "success"), " | ",
colourise("F", "failure"), " ",
colourise("W", "warning"), " ",
colourise("S", "skip"), " ",
colourise(" OK", "success"),
" | ", "Test Directory"
)

summary_results <- as.data.frame(test_results)

sapply(row.names(summary_results), function(summary_row_name) {
path_test_result <- summary_results[summary_row_name, ]

if (path_test_result$failed > 0) {
status <- cli::col_red(cli::symbol$cross)
} else {
status <- cli::col_green(cli::symbol$tick)
}

message <- paste0(
status, " | ",
private$col_format(path_test_result$failed, "fail"), " ",
private$col_format(path_test_result$warning, "warn"), " ",
private$col_format(path_test_result$skipped, "skip"), " ",
sprintf("%3d", path_test_result$passed),
" | ", summary_row_name
)

cli::cat_line(message)
})

private$cat_cr()
},
test_recursive = function(...) {
test_results <- private$run_recursive_test_dir(...)

private$show_summary(test_results)

private$show_final_line(test_results)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation: Use blank lines sparingly, to separate logical blocks of code.

},
cat_cr = function() {
if (cli::is_dynamic_tty()) {
cli::cat_line("\r")
} else {
cli::cat_line("\n")
}
},
col_format = function(n, type) {
if (n == 0) {
" "
} else {
colourise(n, type)
}
}
)
)

colourise <- getFromNamespace("colourise", "testthat")
has_colour <- getFromNamespace("has_colour", "testthat")
summary_line <- getFromNamespace("summary_line", "testthat")
testthat_style <- getFromNamespace("testthat_style", "testthat")

r_cmd_check_fix <- function() {
testthat::test_check()
}
18 changes: 15 additions & 3 deletions R/tools.R
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
#' Run R unit tests
#'
#' Uses the `{testhat}` package to run all unit tests in `tests/testthat` directory.
#' Alternatively, a single unit test file can be provided.
#'
#' @param path Path to file or directory containing tests. Defaults to `tests/testthat`.
#' @param recursive boolean, to run tests on all nested folders inside path. Defaults to TRUE
#' @param ... Additional arguments to pass to `testthat::test_file()` or `testthat::test_dir()`.
#' @return None. This function is called for side effects.
#'
#' @examples
#' if (interactive()) {
#' # Run all unit tests in the `tests/testthat` directory.
#' # Run all unit tests in the `tests/testthat` directory, recursively.
#' test_r()
#'
#' # Run all unit tests in the `tests/testthat` directory only.
#' test_r(recursive = FALSE)
#'
#' # Run one unit test.
#' test_r("tests/testthat/main.R")
#'
#' }
#' @export
test_r <- function() {
testthat::test_dir(fs::path("tests", "testthat"))
test_r <- function(path = fs::path("tests", "testthat"), recursive = TRUE, ...) {
test <- RecursiveUnitTests$new(path = path, recursive = recursive)
test$run_tests(...)
}

lint_dir <- function(path) {
Expand Down
19 changes: 17 additions & 2 deletions man/test_r.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.