Skip to content

Commit 6bb8053

Browse files
Implement expect_shape() (#1469)
Fixes #1423 Co-authored-by: Hadley Wickham <h.wickham@gmail.com>
1 parent 1615d4c commit 6bb8053

20 files changed

+341
-3
lines changed

β€ŽNAMESPACEβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export(expect_s3_class)
119119
export(expect_s4_class)
120120
export(expect_s7_class)
121121
export(expect_setequal)
122+
export(expect_shape)
122123
export(expect_silent)
123124
export(expect_snapshot)
124125
export(expect_snapshot_error)

β€ŽNEWS.mdβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Fixed an issue preventing compilation from succeeding due to deprecation / removal of `std::uncaught_exception()` (@kevinushey, #2047).
77
* 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)
88
* `skip_on_os()` gains an option `"emscripten"` of the `os` argument to skip tests on Emscripten (@eitsupi, #2103).
9+
* New expectation, `expect_shape()`, for testing the shape (i.e., the `length()`, `nrow()`, `ncol()`, or `dim()`), all in one place (#1423, @michaelchirico).
910

1011
# testthat 3.2.3
1112

β€ŽR/expect-length.Rβ€Ž

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#' Does code return a vector with the specified length?
22
#'
3-
#' @seealso [expect_vector()] to make assertions about the "size" of a vector
3+
#' @seealso [expect_vector()] to make assertions about the "size" of a vector,
4+
#' [expect_shape()] for more general assertions about object "shape".
45
#' @inheritParams expect_that
56
#' @param n Expected length.
67
#' @family expectations
@@ -16,11 +17,16 @@ expect_length <- function(object, n) {
1617
stopifnot(is.numeric(n), length(n) == 1)
1718

1819
act <- quasi_label(enquo(object), arg = "object")
20+
expect_length_impl_(act, n)
21+
}
22+
23+
expect_length_impl_ <- function(act, n) {
1924
act$n <- length(act$val)
2025

2126
expect(
2227
act$n == n,
23-
sprintf("%s has length %i, not length %i.", act$lab, act$n, n)
28+
sprintf("%s has length %i, not length %i.", act$lab, act$n, n),
29+
trace_env = parent.frame()
2430
)
2531

2632
invisible(act$val)

β€ŽR/expect-shape.Rβ€Ž

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#' Does code return an object with the specified shape?
2+
#'
3+
#' This is a generalization of [expect_length()] to test the "shape" of
4+
#' more general objects like data.frames, matrices, and arrays.
5+
#'
6+
#' @seealso [expect_length()] to specifically make assertions about the
7+
#' [length()] of a vector.
8+
#' @inheritParams expect_that
9+
#' @param ... Ignored.
10+
#' @param length Expected [length()] of `object`.
11+
#' @param nrow,nrow Expected [nrow()]/[ncol()] of `object`.
12+
#' @param dim Expected [dim()] of `object`.
13+
#' @family expectations
14+
#' @export
15+
#' @examples
16+
#' x <- matrix(1:9, nrow = 3)
17+
#' expect_shape(x, length = 9)
18+
#' expect_shape(x, nrow = 3)
19+
#' expect_shape(x, ncol = 3)
20+
#' expect_shape(x, dim = c(3, 3))
21+
expect_shape = function(object, ..., length, nrow, ncol, dim) {
22+
check_dots_empty()
23+
check_exclusive(length, nrow, ncol, dim)
24+
act <- quasi_label(enquo(object), arg = "object")
25+
26+
# Re-use expect_length() to ensure they stay in sync.
27+
if (!missing(length)) {
28+
return(expect_length_impl_(act, length))
29+
}
30+
# now that we've handled the length argument, revert to usual base function
31+
length <- base::length
32+
33+
dim_object <- base::dim(object)
34+
if (is.null(dim_object)) {
35+
fail(sprintf("%s has no dimensions.", act$lab))
36+
}
37+
38+
if (!missing(nrow)) {
39+
check_number_whole(nrow, allow_na = TRUE)
40+
act$nrow <- dim_object[1L]
41+
42+
expect(
43+
identical(as.integer(act$nrow), as.integer(nrow)),
44+
sprintf("%s has %i rows, not %i.", act$lab, act$nrow, nrow)
45+
)
46+
} else if (!missing(ncol)) {
47+
check_number_whole(ncol, allow_na = TRUE)
48+
49+
if (length(dim_object) == 1L) {
50+
fail(sprintf("%s has only one dimension.", act$lab))
51+
}
52+
53+
act$ncol <- dim_object[2L]
54+
55+
expect(
56+
identical(as.integer(act$ncol), as.integer(ncol)),
57+
sprintf("%s has %i columns, not %i.", act$lab, act$ncol, ncol)
58+
)
59+
} else { # !missing(dim)
60+
if (!is.numeric(dim) && !is.integer(dim)) {
61+
stop_input_type(dim, "a numeric vector")
62+
}
63+
act$dim <- dim_object
64+
65+
if (length(act$dim) != length(dim)) {
66+
fail(sprintf("%s has %i dimensions, not %i.", act$lab, length(act$dim), length(dim)))
67+
}
68+
69+
expect(
70+
identical(as.integer(act$dim), as.integer(dim)),
71+
sprintf("%s has dim (%s), not (%s).", act$lab, toString(act$dim), toString(dim))
72+
)
73+
}
74+
75+
invisible(act$val)
76+
}

β€Ž_pkgdown.ymlβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ reference:
2626
- subtitle: Vectors
2727
contents:
2828
- expect_length
29+
- expect_shape
2930
- expect_gt
3031
- expect_match
3132
- expect_named

β€Žman/comparison-expectations.Rdβ€Ž

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žman/equality-expectations.Rdβ€Ž

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žman/expect_error.Rdβ€Ž

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žman/expect_length.Rdβ€Ž

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žman/expect_match.Rdβ€Ž

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
Β (0)