Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
66b166a
New expectation function, expect_shape()
MichaelChirico Oct 4, 2021
d4853ff
revert spurious NAMESPACE edits
MichaelChirico Oct 4, 2021
69c410a
really this time
MichaelChirico Oct 4, 2021
e89fff9
finish documentation
MichaelChirico Oct 4, 2021
cdb15a2
refactor for clarity, add tests
MichaelChirico Oct 13, 2021
268aee8
Merge branch 'main' into expect-shape
MichaelChirico Jul 24, 2025
89c0c82
restyle NEWS
MichaelChirico Jul 24, 2025
b74737f
move to own file
MichaelChirico Jul 24, 2025
769ccc1
tighter wording: integer-->numeric
MichaelChirico Jul 24, 2025
9aede6f
completeness comment
MichaelChirico Jul 24, 2025
dd23081
new file in pkgdown ref
MichaelChirico Jul 24, 2025
eb0dc5a
Merge branch 'main' into expect-shape
MichaelChirico Jul 24, 2025
1a020dc
reorder news after merge
MichaelChirico Jul 24, 2025
ffde38b
Rearrange to desired signature, adjust tests
MichaelChirico Jul 24, 2025
0661094
tweak NEWS
MichaelChirico Jul 24, 2025
ac0acbc
copy-paste roxygenize...
MichaelChirico Jul 24, 2025
150425b
expect_snapshot() over expect_error()
MichaelChirico Jul 24, 2025
53b0ee8
expect_failure() -> expect_snapshot_failure()
MichaelChirico Jul 24, 2025
a72708b
manual roxygenize continues
MichaelChirico Jul 24, 2025
da7fdf2
manual \description too
MichaelChirico Jul 24, 2025
d873766
rlang::check_exclusive
MichaelChirico Jul 24, 2025
3710870
Check length(dim) to get better errors
MichaelChirico Jul 24, 2025
170543e
new edge tests, improve structure
MichaelChirico Jul 24, 2025
a88340d
more 0-dimension edge case checks
MichaelChirico Jul 24, 2025
e35122a
S3 dispatch check
MichaelChirico Jul 24, 2025
ff4d7c5
Robustness: dim() can return NA
MichaelChirico Jul 24, 2025
4bc32fc
Use fail() + early return, which exposed faulty logic
MichaelChirico Jul 24, 2025
1bdf1cd
update snapshots
MichaelChirico Jul 24, 2025
3ed14c4
remove unneeded early return
MichaelChirico Jul 24, 2025
58654fb
Don't get generic 'object' label by passing to expect_length(); corre…
MichaelChirico Jul 24, 2025
ff4c894
Re-document
hadley Jul 25, 2025
2ca461b
Style tweaks; validate more inputs
hadley Jul 25, 2025
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
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export(expect_s3_class)
export(expect_s4_class)
export(expect_s7_class)
export(expect_setequal)
export(expect_shape)
export(expect_silent)
export(expect_snapshot)
export(expect_snapshot_error)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Fixed an issue preventing compilation from succeeding due to deprecation / removal of `std::uncaught_exception()` (@kevinushey, #2047).
* New `skip_unless_r()` to skip running tests on unsuitable versions of R, e.g. `skip_unless_r(">= 4.1.0")` to skip tests that require, say, `...names` (@MichaelChirico, #2022)
* `skip_on_os()` gains an option `"emscripten"` of the `os` argument to skip tests on Emscripten (@eitsupi, #2103).
* New expectation, `expect_shape()`, for testing the shape (i.e., the `length()`, `nrow()`, `ncol()`, or `dim()`), all in one place (#1423, @michaelchirico).

# testthat 3.2.3

Expand Down
10 changes: 8 additions & 2 deletions R/expect-length.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#' Does code return a vector with the specified length?
#'
#' @seealso [expect_vector()] to make assertions about the "size" of a vector
#' @seealso [expect_vector()] to make assertions about the "size" of a vector,
#' [expect_shape()] for more general assertions about object "shape".
#' @inheritParams expect_that
#' @param n Expected length.
#' @family expectations
Expand All @@ -16,11 +17,16 @@ expect_length <- function(object, n) {
stopifnot(is.numeric(n), length(n) == 1)

act <- quasi_label(enquo(object), arg = "object")
expect_length_impl_(act, n)
}

expect_length_impl_ <- function(act, n) {
Copy link
Member

Choose a reason for hiding this comment

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

I think this is a case where you need to pass trace_env = parent.frame() to expect(). It is slowly starting to come back to me, and I'll document properly soon 😄

act$n <- length(act$val)

expect(
act$n == n,
sprintf("%s has length %i, not length %i.", act$lab, act$n, n)
sprintf("%s has length %i, not length %i.", act$lab, act$n, n),
trace_env = parent.frame()
)

invisible(act$val)
Expand Down
76 changes: 76 additions & 0 deletions R/expect-shape.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#' Does code return an object with the specified shape?
#'
#' This is a generalization of [expect_length()] to test the "shape" of
#' more general objects like data.frames, matrices, and arrays.
#'
#' @seealso [expect_length()] to specifically make assertions about the
#' [length()] of a vector.
#' @inheritParams expect_that
#' @param ... Ignored.
#' @param length Expected [length()] of `object`.
#' @param nrow,nrow Expected [nrow()]/[ncol()] of `object`.
#' @param dim Expected [dim()] of `object`.
#' @family expectations
#' @export
#' @examples
#' x <- matrix(1:9, nrow = 3)
#' expect_shape(x, length = 9)
#' expect_shape(x, nrow = 3)
#' expect_shape(x, ncol = 3)
#' expect_shape(x, dim = c(3, 3))
expect_shape = function(object, ..., length, nrow, ncol, dim) {
check_dots_empty()
check_exclusive(length, nrow, ncol, dim)
act <- quasi_label(enquo(object), arg = "object")

# Re-use expect_length() to ensure they stay in sync.
if (!missing(length)) {
return(expect_length_impl_(act, length))
}
# now that we've handled the length argument, revert to usual base function
length <- base::length

dim_object <- base::dim(object)
if (is.null(dim_object)) {
fail(sprintf("%s has no dimensions.", act$lab))
}

if (!missing(nrow)) {
check_number_whole(nrow, allow_na = TRUE)
act$nrow <- dim_object[1L]

expect(
identical(as.integer(act$nrow), as.integer(nrow)),
sprintf("%s has %i rows, not %i.", act$lab, act$nrow, nrow)
)
} else if (!missing(ncol)) {
check_number_whole(ncol, allow_na = TRUE)

if (length(dim_object) == 1L) {
fail(sprintf("%s has only one dimension.", act$lab))
}

act$ncol <- dim_object[2L]

expect(
identical(as.integer(act$ncol), as.integer(ncol)),
sprintf("%s has %i columns, not %i.", act$lab, act$ncol, ncol)
)
} else { # !missing(dim)
if (!is.numeric(dim) && !is.integer(dim)) {
stop_input_type(dim, "a numeric vector")
}
act$dim <- dim_object

if (length(act$dim) != length(dim)) {
fail(sprintf("%s has %i dimensions, not %i.", act$lab, length(act$dim), length(dim)))
}

expect(
identical(as.integer(act$dim), as.integer(dim)),
sprintf("%s has dim (%s), not (%s).", act$lab, toString(act$dim), toString(dim))
)
}

invisible(act$val)
}
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ reference:
- subtitle: Vectors
contents:
- expect_length
- expect_shape
- expect_gt
- expect_match
- expect_named
Expand Down
1 change: 1 addition & 0 deletions man/comparison-expectations.Rd

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

1 change: 1 addition & 0 deletions man/equality-expectations.Rd

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

1 change: 1 addition & 0 deletions man/expect_error.Rd

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

4 changes: 3 additions & 1 deletion man/expect_length.Rd

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

1 change: 1 addition & 0 deletions man/expect_match.Rd

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

1 change: 1 addition & 0 deletions man/expect_named.Rd

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

1 change: 1 addition & 0 deletions man/expect_null.Rd

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

1 change: 1 addition & 0 deletions man/expect_output.Rd

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

1 change: 1 addition & 0 deletions man/expect_reference.Rd

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

47 changes: 47 additions & 0 deletions man/expect_shape.Rd

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

1 change: 1 addition & 0 deletions man/expect_silent.Rd

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

1 change: 1 addition & 0 deletions man/inheritance-expectations.Rd

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

1 change: 1 addition & 0 deletions man/logical-expectations.Rd

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

89 changes: 89 additions & 0 deletions tests/testthat/_snaps/expect-shape.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# length compared correctly

1 has length 1, not length 2.

# dim compared correctly

matrix(nrow = 6, ncol = 3) has dim (6, 3), not (6, 2).

---

matrix(nrow = 6, ncol = 3) has dim (6, 3), not (7, 3).

---

array(dim = 1:3) has 3 dimensions, not 2.

---

array(dim = 1:3) has 3 dimensions, not 4.

# nrow compared correctly

matrix(nrow = 5, ncol = 5) has 5 rows, not 6.

---

1 has no dimensions.

# ncol compared correctly

matrix(nrow = 5, ncol = 5) has 5 columns, not 7.

---

array(1) has only one dimension.

---

array(integer()) has only one dimension.

# NA handling (e.g. dbplyr)

`x` has NA rows, not 10.

---

`x` has 10 columns, not NA.

---

`x` has dim (NA, 10), not (10, NA).

# checks inputs arguments,

Code
expect_shape(1:10)
Condition
Error in `expect_shape()`:
! One of `length`, `nrow`, `ncol`, or `dim` must be supplied.
Code
expect_shape(1:10, nrow = 1L, ncol = 2L)
Condition
Error in `expect_shape()`:
! Exactly one of `length`, `nrow`, `ncol`, or `dim` must be supplied.
x `nrow` and `ncol` were supplied together.
Code
expect_shape(1:10, 2)
Condition
Error in `expect_shape()`:
! `...` must be empty.
x Problematic argument:
* ..1 = 2
i Did you forget to name an argument?
Code
expect_shape(array(1), nrow = "x")
Condition
Error in `expect_shape()`:
! `nrow` must be a whole number or `NA`, not the string "x".
Code
expect_shape(array(1), ncol = "x")
Condition
Error in `expect_shape()`:
! `ncol` must be a whole number or `NA`, not the string "x".
Code
expect_shape(array(1), dim = "x")
Condition
Error in `expect_shape()`:
! `dim` must be a numeric vector, not the string "x".

Loading
Loading