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

26 add explanation of low code cov #130

Merged
merged 12 commits into from
Nov 13, 2024
6 changes: 3 additions & 3 deletions R/assert_create.R
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ assert_create <- function(func, default_error_msg = NULL){
}

# Ensure func has at least 1 argument
if(func_arg_count(func) == 0){
if(func_arg_count(func, dots = "count_as_0") == 0){
if (func_supports_variable_arguments(func))
additional_note = " (Note '...' does NOT count as an argument)"
else additional_note = ""
Expand All @@ -72,7 +72,7 @@ assert_create <- function(func, default_error_msg = NULL){

# Assert that function has no arguments named 'msg' or 'call', 'arg_name', since we need to add our own
if(any(c('msg', 'call', 'arg_name') %in% names(args))){
cli::cli_abort("Function supplied to `func` argument of `create_dataframe` cannot include paramaters namex 'msg' or 'call', 'arg_name', since we add our own arguments with these names")
cli::cli_abort("Function supplied to `func` argument of `assert_create` cannot include paramaters named 'msg', 'call', or 'arg_name', since assert_create adds these arguments to every assertion")
}

# Change add 'msg', 'call' and 'arg_name' arguments at the end
Expand Down Expand Up @@ -204,7 +204,7 @@ assert_create_chain <- function(...){
))
}

# Check functions all have the required arguments (x, msg & call)
# Check functions all have the required arguments (msg, call and arg_name)
if(!all(vapply(dot_args, function(f){ all(c('msg', 'call', 'arg_name') %in% func_arg_names(f)) }, FUN.VALUE = logical(1)))){
cli::cli_abort(
c("Input to {.strong assert_create_chain} must must be {.strong functions} created by {.strong `assert_create()`}",
Expand Down
10 changes: 6 additions & 4 deletions R/assert_files.R
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ get_file_extensions <- function(filenames) {
}, character(1))
}

#' Title
#' Has Extension
#'
#' @param x object to test
#' @param extensions valid extensions (character vector). Do not include the '.', e.g. supply `extensions = 'txt'` not `extensions = '.txt'`
Expand All @@ -46,16 +46,18 @@ has_extension <- function(x, extensions, compression = FALSE){
all(observed_ext %in% extensions)
}

# Which of the filenames are missing the required extension?
files_missing_extension <- function(x, extensions, compression = FALSE){
original = x
if(compression){
x = sub(x = x,"\\.(gz|bz2|xz)$","")
x = sub(x = x,"\\.(gz|bz2|xz)$","")
}

observed_ext <- get_file_extensions(x)
x[!observed_ext %in% extensions]
original[!observed_ext %in% extensions]
}

# Files ---------------------------------------------------------------
# File Assertions ---------------------------------------------------------------

#' Assert that all files exist
#'
Expand Down
6 changes: 3 additions & 3 deletions R/set_operations.R
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,9 @@ sets_are_equivalent <- function(x, y){
else if(any_missing & !any_extra) "missing"

missing_plural = if(length(missing_values) > 1) "s" else ""
missing_plural_the = if(length(missing_values) < 2) " a " else ""
missing_plural_the = if(length(missing_values) < 2) "a " else ""
extra_plural = if(length(extra_values) > 1) "s" else ""
extra_plural_the = if(length(extra_values) < 2) " an " else ""
extra_plural_the = if(length(extra_values) < 2) "an " else ""


if(failure_mode == "both"){
Expand All @@ -188,6 +188,6 @@ sets_are_equivalent <- function(x, y){
return(paste0("'{arg_name}' contains ", extra_plural_the, "unexpected value",extra_plural,": {setopts_exlusive_to_first(x, y)}."))
}
else if(failure_mode == "missing"){
return(paste0("'{arg_name}' is missing" ,missing_plural_the, " required value",missing_plural,": {setopts_exlusive_to_first(y, x)}."))
return(paste0("'{arg_name}' is missing " ,missing_plural_the, "required value",missing_plural,": {setopts_exlusive_to_first(y, x)}."))
}
}
4 changes: 2 additions & 2 deletions man/has_extension.Rd

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

46 changes: 46 additions & 0 deletions tests/testthat/test-assert_create.R
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,24 @@ cli::test_that_cli(configs = "plain", "created assertion() functions throw infor
expect_true(assert_f1('a', 'b'))
})


cli::test_that_cli(configs = "plain", "assert_create edge case errors", {

# Function has dots
expect_no_error(assert_create(func = function(a, b, ...){ FALSE }))

# Function has dots but no other arguments
expect_error(assert_create(func = function( ...){ FALSE }), regexp = "must have at least 1 paramater.*Note '\\.\\.\\.' does NOT count as an argument")

# Function has names that clash with those assert_create adds to all assertions
expect_error(assert_create(func = function(msg){ FALSE }), regexp = "cannot include paramaters named 'msg', 'call', or 'arg_name", fixed=TRUE)

# arg_name is not a string
assertion <- assert_create(func = function(a){ FALSE }, default_error_msg = "{arg_name} is ignored - this function always throws an error")
expect_error(assertion(a, arg_name = 2), regexp = "arg_name must be a string, not a numeric")
})


# Test Creation of Assertion Chains -----------------------------------------------------
cli::test_that_cli(configs = "plain", "assertion chains can evaluate expressions part and not get confused if they contain variable names", {
#assert_is_character <- assert_create(is.character, "Error: {arg_name} must be a character")
Expand All @@ -130,6 +148,26 @@ cli::test_that_cli(configs = "plain", "assertion chains can evaluate expressions
expect_error(assert_chain(length(y)), regexp = "length(y) must be a character", fixed = TRUE)
})

cli::test_that_cli(configs = "plain", "Common assert_create_chain errors", {

# Throws error if argument given to assert_create_chain is not a function
expect_error(assert_create_chain(
2,
assert_create(is.numeric, "{arg_name} must be numeric")
), regexp = "Input to assert_create_chain must must be functions created by `assert_create()`", fixed=TRUE)

# Throws error a function doesn't have the required arguments (msg, call and arg_name)
expect_error(assert_create_chain(
function(x, msg, arg_name, notcall){},
assert_create(is.numeric, "{arg_name} must be numeric")
), regexp = "Input to assert_create_chain must must be functions created by `assert_create()`", fixed=TRUE)

# Throws error if functions have less than 4 args (some_obj_to_test and officially required functions: msg, call, arg_name)
expect_error(assert_create_chain(
function(msg, call, arg_name){}, # 3 args only
assert_create(is.numeric, "{arg_name} must be numeric")
), regexp = "Input to assert_create_chain must must be functions created by `assert_create()`", fixed=TRUE)
})

cli::test_that_cli(configs = "plain", "assert_create_chain: user supplied custom error message has access to the environment in which it was called", {
assert_chain<- assert_create_chain(
Expand All @@ -152,3 +190,11 @@ cli::test_that_cli(configs = "plain", "assert_create_chain: user supplied custo
age = "26"
expect_error(assert_chain(age, "{arg_name} must be a number, not a {class(arg_value)}"), "age must be a number, not a character", fixed=TRUE)
})


cli::test_that_cli(configs = "plain", "assert_create_chain_example", {
expect_no_error(assert_create_chain_example())
expect_true(is.character(assert_create_chain_example()))
})


130 changes: 130 additions & 0 deletions tests/testthat/test-assert_files.R
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,133 @@ test_that("assert_directory_does_not_exist() works", {
expect_error(assert_file_does_not_exist(dirpath), regexp = "Directory .* already exists")
})


# Test Underlying Functions -----------------------------------------------

## All files exist ------------------------------------------------
test_that("all_files_exist() works", {
# False when files don't exist
expect_false(all_files_exist(c("ALKAWDWLKDLWADJWLD", "ADASKJDLAJWLDJKDKLWDJLAKDJWLKLDAW")))

# Create two temporary files
f1 <- withr::local_tempfile()
f2 <- withr::local_tempfile()
f3 <- "Does Not exist"

# Write some content to the files to ensure they exist
writeLines("Test content 1", f1)
writeLines("Test content 2", f2)

# Test that both files exist
expect_true(file.exists(f1))
expect_true(file.exists(f2))

# Test all_files_exist() function
expect_true(all_files_exist(c(f1, f2)))
expect_false(all_files_exist(c(f1, f2, f3)))
})

## Has Extension ------------------------------------------------
test_that("has_extension works() works", {
# False when files don't exist
expect_false(all_files_exist(c("ALKAWDWLKDLWADJWLD", "ADASKJDLAJWLDJKDKLWDJLAKDJWLKLDAW")))

# Create two temporary files
f1 <- withr::local_tempfile()
f2 <- withr::local_tempfile()
f3 <- "Does Not exist"

# Write some content to the files to ensure they exist
writeLines("Test content 1", f1)
writeLines("Test content 2", f2)

# Test that both files exist
expect_true(file.exists(f1))
expect_true(file.exists(f2))

# Test all_files_exist() function
expect_true(all_files_exist(c(f1, f2)))
expect_false(all_files_exist(c(f1, f2, f3)))
})


test_that("has_extension works with single valid extensions", {
expect_true(has_extension("file.txt", extensions = "txt"))
expect_true(has_extension("file.csv", extensions = "csv"))
expect_false(has_extension("file.doc", extensions = "txt"))
})

test_that("has_extension works with multiple valid extensions", {
expect_true(has_extension("file.txt", extensions = c("txt", "csv")))
expect_true(has_extension("file.csv", extensions = c("txt", "csv")))
expect_false(has_extension("file.doc", extensions = c("txt", "csv")))
})

test_that("has_extension handles multiple files", {
expect_true(has_extension(c("file1.txt", "file2.txt"), extensions = "txt"))
expect_false(has_extension(c("file1.txt", "file2.csv"), extensions = "txt"))
expect_true(has_extension(c("file1.txt", "file2.csv"), extensions = c("txt", "csv")))
})

test_that("has_extension handles compression correctly", {
expect_true(has_extension("file.txt.gz", extensions = "txt", compression = TRUE))
expect_false(has_extension("file.doc.gz", extensions = "txt", compression = TRUE))
expect_true(has_extension("file.txt.bz2", extensions = "txt", compression = TRUE))
expect_true(has_extension("file.csv.xz", extensions = "csv", compression = TRUE))
expect_false(has_extension("file.csv.zip", extensions = "csv", compression = TRUE)) # zip is not supported as compression
})

test_that("has_extension handles files without extensions", {
expect_false(has_extension("file", extensions = "txt"))
expect_false(has_extension("file", extensions = c("txt", "csv")))
expect_true(has_extension("file", extensions = "")) # No extension, treated as valid if `extensions` includes an empty string
})

test_that("has_extension handles mixed cases and unexpected inputs", {
expect_false(has_extension(c("file.TXT", "file.csv"), extensions = "txt")) # Case sensitivity
expect_true(has_extension("file.txt.gz", extensions = "txt", compression = TRUE))
expect_false(has_extension("file.txt.gz", extensions = "gz", compression = TRUE)) # Compression stripped
expect_true(has_extension(c("file.txt", "file.TXT"), extensions = c("txt", "TXT"))) # Mixed-case extensions
})

## Files Missing Extension ------------------------------------------------

test_that("files_missing_extension identifies files without specified extensions", {
# Only "file.doc" does not match the specified extensions
expect_equal(files_missing_extension(c("file.txt", "file.csv", "file.doc"), extensions = c("txt", "csv")),
c("file.doc"))

# Both "file.doc" and "file.pdf" do not match "txt"
expect_equal(files_missing_extension(c("file.txt", "file.doc", "file.pdf"), extensions = "txt"),
c("file.doc", "file.pdf"))

# No files are missing the specified extension
expect_equal(files_missing_extension(c("file.txt", "file.csv"), extensions = c("txt", "csv")),
character(0))
})


test_that("files_missing_extension handles compression correctly", {
# Only "file.doc.gz" does not match "txt" when compression is enabled
expect_equal(files_missing_extension(c("file.txt.gz", "file.csv.bz2", "file.doc.gz"), extensions = "txt", compression = TRUE),
c("file.csv.bz2", "file.doc.gz"))

# When compression is disabled, all files with compression extensions are treated as missing
expect_equal(files_missing_extension(c("file.txt.gz", "file.csv.bz2", "file.doc.gz"), extensions = c("txt", "csv"), compression = FALSE),
c("file.txt.gz", "file.csv.bz2", "file.doc.gz"))

# Mixed case: Only "file.doc.xz" is missing the specified extensions
expect_equal(files_missing_extension(c("file.txt.gz", "file.csv.bz2", "file.doc.xz"), extensions = c("txt", "csv"), compression = TRUE),
c("file.doc.xz"))
})

test_that("files_missing_extension handles files without any extensions", {
# Files without extensions should be identified as missing
expect_equal(files_missing_extension(c("file1", "file2.txt", "file3"), extensions = "txt"),
c("file1", "file3"))

# If extensions includes an empty string, files without extensions should be considered valid
expect_equal(files_missing_extension(c("file1", "file2.txt", "file3"), extensions = c("txt", "")),
character(0))
})

76 changes: 76 additions & 0 deletions tests/testthat/test-assert_functions.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,79 @@

# Test Underlying Functions -----------------------------------------------
# Define some test functions for validation
fn_0_args <- function() {}
fn_1_arg <- function(a) {}
fn_2_args <- function(a, b) {}
fn_1_arg_with_dots <- function(a, ...) {}
fn_2_args_with_dots <- function(a, b, ...) {}

# Unit tests for `function_expects_n_arguments_advanced`
test_that("function_expects_n_arguments_advanced behaves correctly for exact argument count", {
# Exact argument match
expect_true(function_expects_n_arguments_advanced(fn_0_args, 0))
expect_true(function_expects_n_arguments_advanced(fn_1_arg, 1))
expect_true(function_expects_n_arguments_advanced(fn_2_args, 2))

# Mismatched argument counts
expect_match(function_expects_n_arguments_advanced(fn_1_arg, 2), "must expect exactly {.strong {n}} argument", fixed = TRUE)
expect_match(function_expects_n_arguments_advanced(fn_2_args, 1), "must expect exactly {.strong {n}} argument", fixed = TRUE)
})

test_that("function_expects_n_arguments_advanced handles dots behavior correctly", {
# Test with `...` and dots="throw_error"
expect_match(function_expects_n_arguments_advanced(fn_1_arg_with_dots, 1, dots = "throw_error"),
"must not contain ... arguments", fixed = TRUE)

# Test with `...` and dots="count_as_0"
expect_true(function_expects_n_arguments_advanced(fn_1_arg_with_dots, 1, dots = "count_as_0"))
expect_true(function_expects_n_arguments_advanced(fn_2_args_with_dots, 2, dots = "count_as_0"))

# Test with `...` and dots="count_as_1"
expect_true(function_expects_n_arguments_advanced(fn_1_arg_with_dots, 2, dots = "count_as_1"))
expect_true(function_expects_n_arguments_advanced(fn_2_args_with_dots, 3, dots = "count_as_1"))

# Test with `...` and dots="count_as_inf"
expect_true(function_expects_n_arguments_advanced(fn_1_arg_with_dots, Inf, dots = "count_as_inf"))
expect_true(function_expects_n_arguments_advanced(fn_2_args_with_dots, Inf, dots = "count_as_inf"))
})

test_that("function_expects_n_arguments_advanced returns correct error for non-function inputs", {
# Input is not a function
expect_match(function_expects_n_arguments_advanced(42, 1), "must be a function, not a", fixed = TRUE)
expect_match(function_expects_n_arguments_advanced("not_a_function", 1), "must be a function, not a", fixed = TRUE)
})

test_that("function_expects_n_arguments_advanced correctly counts arguments for functions with no arguments", {
# Functions with no arguments should pass with 0 expected arguments
expect_true(function_expects_n_arguments_advanced(fn_0_args, 0))

# Mismatch for functions with no arguments
expect_match(function_expects_n_arguments_advanced(fn_0_args, 1), "must expect exactly {.strong {n}} argument", fixed = TRUE)
})

test_that("function_expects_n_arguments_advanced correctly counts arguments for functions with multiple arguments", {
# Functions with multiple arguments should match exactly
expect_true(function_expects_n_arguments_advanced(fn_2_args, 2))

# Mismatch for functions with multiple arguments
expect_match(function_expects_n_arguments_advanced(fn_2_args, 3), "must expect exactly {.strong {n}} argument", fixed = TRUE)
})

test_that("function_expects_n_arguments_advanced handles `dots` parameter correctly with complex cases", {
# Functions with variadic args (dots) should adapt based on `dots` parameter
expect_true(function_expects_n_arguments_advanced(fn_1_arg_with_dots, 1, dots = "count_as_0"))
expect_true(function_expects_n_arguments_advanced(fn_2_args_with_dots, 2, dots = "count_as_0"))
expect_true(function_expects_n_arguments_advanced(fn_1_arg_with_dots, 2, dots = "count_as_1"))
expect_true(function_expects_n_arguments_advanced(fn_2_args_with_dots, 3, dots = "count_as_1"))
expect_true(function_expects_n_arguments_advanced(fn_1_arg_with_dots, Inf, dots = "count_as_inf"))
expect_true(function_expects_n_arguments_advanced(fn_2_args_with_dots, Inf, dots = "count_as_inf"))
})




# Test Assertions ---------------------------------------------------------

cli::test_that_cli("assert_function_expects_n_arguments() works", config = "plain", {
# Works for functions with the correct number of arguments
my_func <- function(x, y) { x + y }
Expand Down
Loading
Loading