Skip to content

Commit 557f929

Browse files
authored
Merge branch 'main' into parallel-max-fail
2 parents 40573f0 + 7b71880 commit 557f929

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+747
-129
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export(expect_no_warning)
114114
export(expect_null)
115115
export(expect_output)
116116
export(expect_output_file)
117+
export(expect_r6_class)
117118
export(expect_reference)
118119
export(expect_s3_class)
119120
export(expect_s4_class)

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# testthat (development version)
22

33
* `ParallelProgressReporter` now respect `max_failures` (#1162).
4+
* New `expect_r6_class()` (#2030).
5+
* `expect_*()` functions consistently and rigorously check their inputs (#1754).
46
* `JunitReporter()` no longer fails with `"no applicable method for xml_add_child"` for warnings outside of tests (#1913). Additionally, warnings now save their backtraces.
57
* `JunitReporter()` strips ANSI escapes in more placese (#1852, #2032).
68
* `try_again()` is now publicised. The first argument is now the number of retries, not tries (#2050).

R/auto-test.R

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
11
#' Watches code and tests for changes, rerunning tests as appropriate.
22
#'
3+
#' @description
4+
#' `r lifecycle::badge("superseded")`
5+
#'
36
#' The idea behind `auto_test()` is that you just leave it running while
47
#' you develop your code. Every time you save a file it will be automatically
58
#' tested and you can easily see if your changes have caused any test
6-
#' failures.
9+
#' failures.
710
#'
811
#' The current strategy for rerunning tests is as follows:
912
#'
1013
#' - if any code has changed, then those files are reloaded and all tests
1114
#' rerun
1215
#' - otherwise, each new or modified test is run
13-
#'
14-
#' In the future, `auto_test()` might implement one of the following more
15-
#' intelligent alternatives:
16-
#'
17-
#' - Use codetools to build up dependency tree and then rerun tests only
18-
#' when a dependency changes.
19-
#' - Mimic ruby's autotest and rerun only failing tests until they pass,
20-
#' and then rerun all tests.
21-
#
2216
#' @seealso [auto_test_package()]
2317
#' @export
2418
#' @param code_path path to directory containing code
@@ -27,7 +21,7 @@
2721
#' @param env environment in which to execute test suite.
2822
#' @param hash Passed on to [watch()]. When FALSE, uses less accurate
2923
#' modification time stamps, but those are faster for large files.
30-
#' @keywords debugging
24+
#' @keywords internal
3125
auto_test <- function(
3226
code_path,
3327
test_path,
@@ -67,15 +61,12 @@ auto_test <- function(
6761
watch(c(code_path, test_path), watcher, hash = hash)
6862
}
6963

70-
#' Watches a package for changes, rerunning tests as appropriate.
71-
#'
7264
#' @param pkg path to package
7365
#' @export
7466
#' @param reporter test reporter to use
7567
#' @param hash Passed on to [watch()]. When FALSE, uses less accurate
7668
#' modification time stamps, but those are faster for large files.
77-
#' @keywords debugging
78-
#' @seealso [auto_test()] for details on how method works
69+
#' @rdname auto_test
7970
auto_test_package <- function(
8071
pkg = ".",
8172
reporter = default_reporter(),

R/compare.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ compare <- function(x, y, ...) {
1616
}
1717

1818
comparison <- function(equal = TRUE, message = "Equal") {
19-
stopifnot(is.logical(equal), length(equal) == 1)
20-
stopifnot(is.character(message))
19+
check_bool(equal)
20+
check_character(message)
2121

2222
structure(
2323
list(

R/expect-condition.R

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ expect_error <- function(
114114
info = NULL,
115115
label = NULL
116116
) {
117+
check_string(regexp, allow_null = TRUE, allow_na = TRUE)
118+
check_string(class, allow_null = TRUE)
119+
check_bool(inherit)
120+
117121
if (edition_get() >= 3) {
118122
expect_condition_matching_(
119123
"error",
@@ -158,6 +162,11 @@ expect_warning <- function(
158162
info = NULL,
159163
label = NULL
160164
) {
165+
check_string(regexp, allow_null = TRUE, allow_na = TRUE)
166+
check_string(class, allow_null = TRUE)
167+
check_bool(inherit)
168+
check_bool(all)
169+
161170
if (edition_get() >= 3) {
162171
if (!missing(all)) {
163172
warn("The `all` argument is deprecated")
@@ -207,6 +216,11 @@ expect_message <- function(
207216
info = NULL,
208217
label = NULL
209218
) {
219+
check_string(regexp, allow_null = TRUE, allow_na = TRUE)
220+
check_string(class, allow_null = TRUE)
221+
check_bool(inherit)
222+
check_bool(all)
223+
210224
if (edition_get() >= 3) {
211225
expect_condition_matching_(
212226
"message",
@@ -239,6 +253,10 @@ expect_condition <- function(
239253
info = NULL,
240254
label = NULL
241255
) {
256+
check_string(regexp, allow_null = TRUE, allow_na = TRUE)
257+
check_string(class, allow_null = TRUE)
258+
check_bool(inherit)
259+
242260
if (edition_get() >= 3) {
243261
expect_condition_matching_(
244262
"condition",

R/expect-equality.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ expect_equal <- function(
6565
) {
6666
act <- quasi_label(enquo(object), label)
6767
exp <- quasi_label(enquo(expected), expected.label)
68+
check_number_decimal(tolerance, min = 0, allow_null = TRUE)
6869

6970
if (edition_get() >= 3) {
7071
expect_waldo_equal_("equal", act, exp, info, ..., tolerance = tolerance)

R/expect-inheritance.R

Lines changed: 83 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,24 @@
1212
#' * `expect_s4_class(x, class)` checks that `x` is an S4 object that
1313
#' [is()] `class`.
1414
#' * `expect_s4_class(x, NA)` checks that `x` isn't an S4 object.
15+
#' * `expect_r6_class(x, class)` checks that `x` an R6 object that
16+
#' inherits from `class`.
1517
#' * `expect_s7_class(x, Class)` checks that `x` is an S7 object that
1618
#' [S7::S7_inherits()] from `Class`
1719
#'
1820
#' See [expect_vector()] for testing properties of objects created by vctrs.
1921
#'
2022
#' @param type String giving base type (as returned by [typeof()]).
21-
#' @param class Either a character vector of class names, or
22-
#' for `expect_s3_class()` and `expect_s4_class()`, an `NA` to assert
23-
#' that `object` isn't an S3 or S4 object.
23+
#' @param class
24+
#' * `expect_type()`: a single string giving an R base type.
25+
#' * `expect_s3_class()`: a character vector of class names or `NA` to assert
26+
#' that `object` isn't an S3 object. If you provide multiple class names,
27+
#' the test will pass if `object` inherits from any of them, unless
28+
#' `exact = TRUE`.
29+
#' * `expect_s4_class()`: a character vector of class names or `NA` to assert
30+
#' that `object` isn't an S4 object.
31+
#' * `expect_r6_class()`: a string.
32+
#' * `expect_s7_class()`: an [S7::S7_class()] object.
2433
#' @inheritParams expect_that
2534
#' @family expectations
2635
#' @examples
@@ -31,6 +40,15 @@
3140
#' # A data frame is built from a list:
3241
#' expect_type(x, "list")
3342
#'
43+
#' f <- factor(c("a", "b", "c"))
44+
#' o <- ordered(f)
45+
#'
46+
#' # Using multiple class names tests if the object inherits from any of them
47+
#' expect_s3_class(f, c("ordered", "factor"))
48+
#' # Use exact = TRUE to test for exact match
49+
#' show_failure(expect_s3_class(f, c("ordered", "factor"), exact = TRUE))
50+
#' expect_s3_class(o, c("ordered", "factor"), exact = TRUE)
51+
#'
3452
#' # An integer vector is an atomic vector of type "integer"
3553
#' expect_type(x$x, "integer")
3654
#' # It is not an S3 object
@@ -46,7 +64,7 @@ NULL
4664
#' @export
4765
#' @rdname inheritance-expectations
4866
expect_type <- function(object, type) {
49-
stopifnot(is.character(type), length(type) == 1)
67+
check_string(type)
5068

5169
act <- quasi_label(enquo(object))
5270
act_type <- typeof(act$val)
@@ -66,9 +84,11 @@ expect_type <- function(object, type) {
6684
#' @export
6785
#' @rdname inheritance-expectations
6886
#' @param exact If `FALSE`, the default, checks that `object` inherits
69-
#' from `class`. If `TRUE`, checks that object has a class that's identical
70-
#' to `class`.
87+
#' from any element of `class`. If `TRUE`, checks that object has a class
88+
#' that's identical to `class`.
7189
expect_s3_class <- function(object, class, exact = FALSE) {
90+
check_bool(exact)
91+
7292
act <- quasi_label(enquo(object))
7393
act$class <- format_class(class(act$val))
7494
exp_lab <- format_class(class)
@@ -98,36 +118,7 @@ expect_s3_class <- function(object, class, exact = FALSE) {
98118
}
99119
}
100120
} else {
101-
abort("`class` must be a NA or a character vector")
102-
}
103-
104-
pass(act$val)
105-
}
106-
107-
#' @export
108-
#' @rdname inheritance-expectations
109-
expect_s7_class <- function(object, class) {
110-
check_installed("S7")
111-
if (!inherits(class, "S7_class")) {
112-
stop_input_type(class, "an S7 class object")
113-
}
114-
115-
act <- quasi_label(enquo(object))
116-
117-
if (!S7::S7_inherits(object)) {
118-
return(fail(sprintf("%s is not an S7 object", act$lab)))
119-
}
120-
121-
if (!S7::S7_inherits(object, class)) {
122-
obj_class <- setdiff(base::class(object), "S7_object")
123-
class_desc <- paste0("<", obj_class, ">", collapse = "/")
124-
msg <- sprintf(
125-
"%s inherits from %s not <%s>.",
126-
act$lab,
127-
class_desc,
128-
attr(class, "name", TRUE)
129-
)
130-
return(fail(msg))
121+
stop_input_type(class, c("a character vector", "NA"))
131122
}
132123

133124
pass(act$val)
@@ -160,13 +151,60 @@ expect_s4_class <- function(object, class) {
160151
}
161152
}
162153
} else {
163-
abort("`class` must be a NA or a character vector")
154+
stop_input_type(class, c("a character vector", "NA"))
164155
}
165156

166157
pass(act$val)
167158
}
168159

169-
isS3 <- function(x) is.object(x) && !isS4(x)
160+
#' @export
161+
#' @rdname inheritance-expectations
162+
expect_r6_class <- function(object, class) {
163+
act <- quasi_label(enquo(object))
164+
check_string(class)
165+
166+
if (!inherits(act$val, "R6")) {
167+
return(fail(sprintf("%s is not an R6 object.", act$lab)))
168+
}
169+
170+
if (!inherits(act$val, class)) {
171+
act_class <- format_class(class(act$val))
172+
exp_class <- format_class(class)
173+
msg <- sprintf("%s inherits from %s not %s.", act$lab, act_class, exp_class)
174+
return(fail(msg))
175+
}
176+
177+
pass(act$val)
178+
}
179+
180+
#' @export
181+
#' @rdname inheritance-expectations
182+
expect_s7_class <- function(object, class) {
183+
check_installed("S7")
184+
if (!inherits(class, "S7_class")) {
185+
stop_input_type(class, "an S7 class object")
186+
}
187+
188+
act <- quasi_label(enquo(object))
189+
190+
if (!S7::S7_inherits(object)) {
191+
return(fail(sprintf("%s is not an S7 object", act$lab)))
192+
}
193+
194+
if (!S7::S7_inherits(object, class)) {
195+
obj_class <- setdiff(base::class(object), "S7_object")
196+
class_desc <- paste0("<", obj_class, ">", collapse = "/")
197+
msg <- sprintf(
198+
"%s inherits from %s not <%s>.",
199+
act$lab,
200+
class_desc,
201+
attr(class, "name", TRUE)
202+
)
203+
return(fail(msg))
204+
}
205+
206+
pass(act$val)
207+
}
170208

171209
#' Does an object inherit from a given class?
172210
#'
@@ -175,19 +213,20 @@ isS3 <- function(x) is.object(x) && !isS4(x)
175213
#'
176214
#' `expect_is()` is an older form that uses [inherits()] without checking
177215
#' whether `x` is S3, S4, or neither. Instead, I'd recommend using
178-
#' [expect_type()], [expect_s3_class()] or [expect_s4_class()] to more clearly
179-
#' convey your intent.
216+
#' [expect_type()], [expect_s3_class()], or [expect_s4_class()] to more clearly
217+
#' convey your intent.
180218
#'
181219
#' @section 3rd edition:
182220
#' `r lifecycle::badge("deprecated")`
183221
#'
184222
#' `expect_is()` is formally deprecated in the 3rd edition.
185223
#'
186224
#' @keywords internal
225+
#' @param class Class name passed to `inherits()`.
187226
#' @inheritParams expect_type
188227
#' @export
189228
expect_is <- function(object, class, info = NULL, label = NULL) {
190-
stopifnot(is.character(class))
229+
check_character(class)
191230
edition_deprecate(
192231
3,
193232
"expect_is()",
@@ -210,6 +249,9 @@ expect_is <- function(object, class, info = NULL, label = NULL) {
210249
pass(act$val)
211250
}
212251

252+
# Helpers ----------------------------------------------------------------------
253+
254+
isS3 <- function(x) is.object(x) && !isS4(x)
213255

214256
format_class <- function(x) {
215257
paste0(encodeString(x, quote = "'"), collapse = "/")

R/expect-known.R

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ expect_known_output <- function(
5858
print = FALSE,
5959
width = 80
6060
) {
61+
check_string(file)
62+
check_bool(update)
63+
check_bool(print)
64+
check_number_whole(width, min = 1)
6165
edition_deprecate(
6266
3,
6367
"expect_known_output()",
@@ -131,6 +135,10 @@ expect_output_file <- function(
131135
print = FALSE,
132136
width = 80
133137
) {
138+
check_string(file)
139+
check_bool(update)
140+
check_bool(print)
141+
check_number_whole(width, min = 1)
134142
# Code is a copy of expect_known_output()
135143
edition_deprecate(
136144
3,
@@ -158,6 +166,9 @@ expect_known_value <- function(
158166
label = NULL,
159167
version = 2
160168
) {
169+
check_string(file)
170+
check_bool(update)
171+
check_number_whole(version, min = 1)
161172
edition_deprecate(
162173
3,
163174
"expect_known_value()",

R/expect-length.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
#' expect_length(1:10, 1)
1515
#' }
1616
expect_length <- function(object, n) {
17-
stopifnot(is.numeric(n), length(n) == 1)
17+
check_number_whole(n, min = 0)
1818

1919
act <- quasi_label(enquo(object))
2020
act$n <- length(act$val)

R/expect-match.R

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,11 @@ expect_no_match <- function(
7777
) {
7878
# Capture here to avoid environment-related messiness
7979
act <- quasi_label(enquo(object), label)
80-
stopifnot(is.character(regexp), length(regexp) == 1)
81-
stopifnot(is.character(act$val))
80+
check_character(object)
81+
check_string(regexp)
82+
check_bool(perl)
83+
check_bool(fixed)
84+
check_bool(all)
8285

8386
expect_match_(
8487
act = act,

0 commit comments

Comments
 (0)