Skip to content

Check that the f passed to epi[x]_slide takes enough args #302

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

Merged
merged 21 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f0465c4
check num args before ... in f
nmdefries Apr 20, 2023
138cdb0
only warn if dots provided in args
nmdefries Apr 21, 2023
e2c8152
test num args errors and warnings
nmdefries Apr 21, 2023
95fc474
use older support for not raising errors in tests
nmdefries Apr 24, 2023
066fb4e
factor out args check
nmdefries Apr 24, 2023
ed2a5e9
test slide fn and arg check fn arg warnings
nmdefries Apr 24, 2023
1ec3597
reduce slide arg check test coverage
nmdefries Apr 24, 2023
a3f227c
Rename check->assert_sufficient_f_args, tweak warning text & fields
lcbrooks May 8, 2023
6825a5c
rename error class to match func name
nmdefries May 10, 2023
51b12ee
drop regexp in tests where also specify error class
nmdefries May 10, 2023
d5466d3
check if required fields already have defaults set
nmdefries May 11, 2023
46cf783
test default checking
nmdefries May 11, 2023
a572464
Suppress forwarded warning from warning+error sufficient-args test
lcbrooks May 15, 2023
b982526
Account for `...` forwarding in `assert_sufficient_f_args`
lcbrooks May 16, 2023
290b4b9
Consider unnamed dots forwarding in `assert_sufficient_f_args`
lcbrooks May 16, 2023
34649f8
factor out "dots_i -1" to var
nmdefries May 17, 2023
6ebb1a5
import tail
nmdefries May 17, 2023
f6836d3
Message about right args when `f` default is suspiciously replaced
brookslogan May 18, 2023
10fb9e4
Message about right args when they fall into `f`'s dots
lcbrooks May 18, 2023
f0f0105
Also message about args fed to `f` dots when it has dots first
lcbrooks May 18, 2023
a872728
Fix and test some other corner cases in f arg checking
lcbrooks May 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions R/grouped_epi_archive.R
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ grouped_epi_archive =
ref_time_values = sort(ref_time_values)
}

# Check that `f` takes enough args
if (!missing(f) && is.function(f)) {
check_sufficient_f_args(f)
}

# Validate and pre-process `before`:
if (missing(before)) {
Abort("`before` is required (and must be passed by name);
Expand Down
7 changes: 6 additions & 1 deletion R/slide.R
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,12 @@ epi_slide = function(x, f, ..., before, after, ref_time_values,

# Check we have an `epi_df` object
if (!inherits(x, "epi_df")) Abort("`x` must be of class `epi_df`.")


# Check that `f` takes enough args
if (!missing(f) && is.function(f)) {
check_sufficient_f_args(f)
}

# Arrange by increasing time_value
x = arrange(x, time_value)

Expand Down
30 changes: 30 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,36 @@ paste_lines = function(lines) {
Abort = function(msg, ...) rlang::abort(break_str(msg, init = "Error: "), ...)
Warn = function(msg, ...) rlang::warn(break_str(msg, init = "Warning: "), ...)

#' Check that a sliding computation function takes enough args
#'
#' @param f Function; specifies a computation to slide over an `epi_df` or
#' `epi_archive` in `epi_slide` or `epix_slide`.
#'
#' @noRd
check_sufficient_f_args <- function(f) {
n_mandatory_f_args <- 2
arg_names = names(formals(args(f)))
if ("..." %in% arg_names) {
# Keep all arg names before `...`
dots_i <- which(arg_names == "...")
arg_names <- arg_names[seq_len(dots_i - 1)]

if (length(arg_names) < n_mandatory_f_args) {
Warn(sprintf("`f` only takes %s positional arguments before the `...` args, but %s were expected; this can lead to obtuse errors downstream", length(arg_names), n_mandatory_f_args),
class="check_sufficient_f_args__f_needs_min_args_before_dots",
epiprocess__f = f,
epiprocess__arg_names = arg_names)
}
} else {
if (length(arg_names) < n_mandatory_f_args) {
Abort(sprintf("`f` must take at least %s arguments", n_mandatory_f_args),
class="check_sufficient_f_args__f_needs_min_args",
epiprocess__f = f,
epiprocess__arg_names = arg_names)
}
}
}

##########

in_range = function(x, rng) pmin(pmax(x, rng[1]), rng[2])
Expand Down
12 changes: 12 additions & 0 deletions tests/testthat/test-epi_slide.R
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,15 @@ test_that("these doesn't produce an error; the error appears only if the ref tim
dplyr::select("geo_value","slide_value_value"),
dplyr::tibble(geo_value = c("ak", "al"), slide_value_value = c(2, -2))) # not out of range for either group
})

test_that("epi_slide alerts if the provided f doesn't take enough args", {
f_xg = function(x, g) dplyr::tibble(value=mean(x$value), count=length(x$value))
# If `regexp` is NA, asserts that there should be no errors/messages.
expect_error(epi_slide(grouped, f_xg, before = 1L, ref_time_values = d+1), regexp = NA)
expect_warning(epi_slide(grouped, f_xg, before = 1L, ref_time_values = d+1), regexp = NA)

f_x_dots = function(x, ...) dplyr::tibble(value=mean(x$value), count=length(x$value))
expect_warning(epi_slide(grouped, f_x_dots, before = 1L, ref_time_values = d+1),
regexp = "positional arguments before the `...` args",
class = "check_sufficient_f_args__f_needs_min_args_before_dots")
})
12 changes: 12 additions & 0 deletions tests/testthat/test-epix_slide.R
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,15 @@ test_that("epix_slide with all_versions option works as intended",{

expect_identical(xx1,xx3) # This and * Imply xx2 and xx3 are identical
})

test_that("epix_slide alerts if the provided f doesn't take enough args", {
f_xg = function(x, g) dplyr::tibble(value=mean(x$binary), count=length(x$binary))
# If `regexp` is NA, asserts that there should be no errors/messages.
expect_error(epix_slide(xx, f = f_xg, before = 2L), regexp = NA)
expect_warning(epix_slide(xx, f = f_xg, before = 2L), regexp = NA)

f_x_dots = function(x, ...) dplyr::tibble(value=mean(x$binary), count=length(x$binary))
expect_warning(epix_slide(xx, f_x_dots, before = 2L),
regexp = "positional arguments before the `...` args",
class = "check_sufficient_f_args__f_needs_min_args_before_dots")
})
31 changes: 30 additions & 1 deletion tests/testthat/test-utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,33 @@ test_that("enlist works",{
my_list <- enlist(x=1,y=2,z=3)
expect_equal(my_list$x,1)
expect_true(inherits(my_list,"list"))
})
})

test_that("check_sufficient_f_args alerts if the provided f doesn't take enough args", {
f_xg = function(x, g) dplyr::tibble(value=mean(x$binary), count=length(x$binary))
f_xg_dots = function(x, g, ...) dplyr::tibble(value=mean(x$binary), count=length(x$binary))

# If `regexp` is NA, asserts that there should be no errors/messages.
expect_error(check_sufficient_f_args(f_xg), regexp = NA)
expect_warning(check_sufficient_f_args(f_xg), regexp = NA)
expect_error(check_sufficient_f_args(f_xg_dots), regexp = NA)
expect_warning(check_sufficient_f_args(f_xg_dots), regexp = NA)

f_x_dots = function(x, ...) dplyr::tibble(value=mean(x$binary), count=length(x$binary))
f_dots = function(...) dplyr::tibble(value=c(5), count=c(2))
f_x = function(x) dplyr::tibble(value=mean(x$binary), count=length(x$binary))
f = function() dplyr::tibble(value=c(5), count=c(2))

expect_warning(check_sufficient_f_args(f_x_dots),
regexp = "positional arguments before the `...` args",
class = "check_sufficient_f_args__f_needs_min_args_before_dots")
expect_warning(check_sufficient_f_args(f_dots),
regexp = "positional arguments before the `...` args",
class = "check_sufficient_f_args__f_needs_min_args_before_dots")
expect_error(check_sufficient_f_args(f_x),
regexp = "`f` must take at least",
class = "check_sufficient_f_args__f_needs_min_args")
expect_error(check_sufficient_f_args(f),
regexp = "`f` must take at least",
class = "check_sufficient_f_args__f_needs_min_args")
})