Skip to content

Commit 3c45e5c

Browse files
authored
Make succeed()/failure() primary (#2125)
Instead of `expect()`. I think this makes the reasoning a little easier to follow, and will support the transition away from `expect()`. Now used in the vignette, but not yet in testthat itself; that's the next job.
1 parent 222ddcd commit 3c45e5c

File tree

16 files changed

+168
-154
lines changed

16 files changed

+168
-154
lines changed

β€ŽNAMESPACEβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ export(make_expectation)
164164
export(mock_output_sequence)
165165
export(new_expectation)
166166
export(not)
167+
export(pass)
167168
export(prints_text)
168169
export(quasi_label)
169170
export(run_cpp_tests)

β€ŽNEWS.mdβ€Ž

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

3+
* New `pass()` function to use in place of `succeed()` (#2113).
4+
* `expectation()` is now a combination of `new_expectation()` and `exp_signal()` (#2125).
35
* `is_null()`/`matches()` deprecated in 2.0.0 (2017-12-19) and `is_true()`/`is_false()` deprecated in 2.1.0 (2019-04-23) have been removed (#2109).
46
* `local_edition()` now gives a useful error for bad values (#1547).
57
* testthat now requires R 4.1.

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

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,24 @@ expect_that <- function(object, condition, info = NULL, label = NULL) {
3636
condition(object)
3737
}
3838

39-
#' Default expectations that always succeed or fail.
39+
#' `pass()` or `fail()` a test
4040
#'
41-
#' These allow you to manually trigger success or failure. Failure is
42-
#' particularly useful to a pre-condition or mark a test as not yet
43-
#' implemented.
41+
#' @description
42+
#' These are the primitives that you can use to implement your own expectations.
43+
#' Every branch of code inside an expectation must call either `pass()` or
44+
#' `fail()`; learn more in `vignette("custom-expectation")`.
4445
#'
4546
#' @param message a string to display.
46-
#' @inheritParams expect
47+
#' @param info Character vector continuing additional information. Included
48+
#' for backward compatibility only and new expectations should not use it.
49+
#' @param srcref Location of the failure. Should only needed to be explicitly
50+
#' supplied when you need to forward a srcref captured elsewhere.
51+
#' @param trace An optional backtrace created by [rlang::trace_back()].
52+
#' When supplied, the expectation is displayed with the backtrace.
53+
#' @param trace_env If `is.null(trace)`, this is used to automatically
54+
#' generate a traceback running from `test_code()`/`test_file()` to
55+
#' `trace_env`. You'll generally only need to set this if you're wrapping
56+
#' an expectation inside another function.
4757
#' @export
4858
#' @examples
4959
#' \dontrun{
@@ -53,13 +63,41 @@ expect_that <- function(object, condition, info = NULL, label = NULL) {
5363
fail <- function(
5464
message = "Failure has been forced",
5565
info = NULL,
56-
trace_env = caller_env()
66+
srcref = NULL,
67+
trace_env = caller_env(),
68+
trace = NULL
5769
) {
58-
expect(FALSE, message, info = info, trace_env = trace_env)
70+
if (is.null(trace)) {
71+
trace <- trace_back(top = getOption("testthat_topenv"), bottom = trace_env)
72+
}
73+
# Only show if there's at least one function apart from the expectation
74+
if (trace_length(trace) <= 1) {
75+
trace <- NULL
76+
}
77+
78+
message <- paste(c(message, info), collapse = "\n")
79+
expectation("failure", message, srcref = srcref, trace = trace)
5980
}
6081

6182
#' @rdname fail
83+
#' @param value Value to return, typically the result of evaluating the
84+
#' `object` argument to the expectation.
6285
#' @export
86+
pass <- function(value) {
87+
expectation("success", "success")
88+
invisible(value)
89+
}
90+
91+
#' Mark a test as successful
92+
#'
93+
#' This is an older version of [pass()] that exists for backwards compatibility.
94+
#' You should now use `pass()` instead`
95+
#'
96+
#' @export
97+
#' @inheritParams fail
98+
#' @keywords internal
6399
succeed <- function(message = "Success has been forced", info = NULL) {
64-
expect(TRUE, message, info = info)
100+
message <- paste(c(message, info), collapse = "\n")
101+
102+
expectation("success", message)
65103
}

β€ŽR/expectation.Rβ€Ž

Lines changed: 26 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,9 @@
55
#'
66
#' @param ok `TRUE` or `FALSE` indicating if the expectation was successful.
77
#' @param failure_message Message to show if the expectation failed.
8-
#' @param info Character vector continuing additional information. Included
9-
#' for backward compatibility only and new expectations should not use it.
10-
#' @param srcref Location of the failure. Should only needed to be explicitly
11-
#' supplied when you need to forward a srcref captured elsewhere.
12-
#' @param trace An optional backtrace created by [rlang::trace_back()].
13-
#' When supplied, the expectation is displayed with the backtrace.
14-
#' @param trace_env If `is.null(trace)`, this is used to automatically
15-
#' generate a traceback running from `test_code()`/`test_file()` to
16-
#' `trace_env`. You'll generally only need to set this if you're wrapping
17-
#' an expectation inside another function.
18-
#' @return An expectation object. Signals the expectation condition
8+
#' @inheritParams fail
9+
#' @return An expectation object from either `succeed()` or `fail()`.
1910
#' with a `continue_test` restart.
20-
#'
21-
#' @details
22-
#'
23-
#' While `expect()` creates and signals an expectation in one go,
24-
#' `exp_signal()` separately signals an expectation that you
25-
#' have manually created with [new_expectation()]. Expectations are
26-
#' signalled with the following protocol:
27-
#'
28-
#' * If the expectation is a failure or an error, it is signalled with
29-
#' [base::stop()]. Otherwise, it is signalled with
30-
#' [base::signalCondition()].
31-
#'
32-
#' * The `continue_test` restart is registered. When invoked, failing
33-
#' expectations are ignored and normal control flow is resumed to
34-
#' run the other tests.
35-
#'
3611
#' @seealso [exp_signal()]
3712
#' @export
3813
expect <- function(
@@ -43,58 +18,39 @@ expect <- function(
4318
trace = NULL,
4419
trace_env = caller_env()
4520
) {
46-
type <- if (ok) "success" else "failure"
47-
48-
# Preserve existing API which appear to be used in package test code
49-
# Can remove in next major release
50-
if (missing(failure_message)) {
51-
warn("`failure_message` is missing, with no default.")
52-
message <- "unknown failure"
21+
if (ok) {
22+
succeed(failure_message)
5323
} else {
54-
# A few packages include code in info that errors on evaluation
55-
if (ok) {
56-
message <- paste(failure_message, collapse = "\n")
57-
} else {
58-
message <- paste(c(failure_message, info), collapse = "\n")
59-
}
60-
}
61-
62-
if (!ok) {
63-
if (is.null(trace)) {
64-
trace <- trace_back(
65-
top = getOption("testthat_topenv"),
66-
bottom = trace_env
67-
)
68-
}
69-
70-
# Only show if there's at least one function apart from the expectation
71-
if (trace_length(trace) <= 1) {
72-
trace <- NULL
73-
}
24+
fail(
25+
failure_message,
26+
info,
27+
srcref = srcref,
28+
trace = trace,
29+
trace_env = trace_env
30+
)
7431
}
75-
76-
exp <- expectation(type, message, srcref = srcref, trace = trace)
77-
exp_signal(exp)
7832
}
7933

80-
8134
#' Construct an expectation object
8235
#'
36+
#' @description
8337
#' For advanced use only. If you are creating your own expectation, you should
84-
#' call [expect()] instead. See `vignette("custom-expectation")` for more
38+
#' call [pass()] or [fail()]. See `vignette("custom-expectation")` for more
8539
#' details.
8640
#'
87-
#' Create an expectation with `expectation()` or `new_expectation()`
88-
#' and signal it with `exp_signal()`.
41+
#' `new_expectation()` creates an expectation object and `exp_signal()` signals
42+
#' it. `expectation()` does both.
8943
#'
9044
#' @param type Expectation type. Must be one of "success", "failure", "error",
9145
#' "skip", "warning".
9246
#' @param message Message describing test failure
9347
#' @param srcref Optional `srcref` giving location of test.
48+
#' @keywords internal
9449
#' @inheritParams expect
9550
#' @export
9651
expectation <- function(type, message, srcref = NULL, trace = NULL) {
97-
new_expectation(type, message, srcref = srcref, trace = trace)
52+
exp <- new_expectation(type, message, srcref = srcref, trace = trace)
53+
exp_signal(exp)
9854
}
9955
#' @rdname expectation
10056
#' @param ... Additional attributes for the expectation object.
@@ -207,7 +163,7 @@ as.expectation.error <- function(x, srcref = NULL) {
207163
cnd_message(x)
208164
)
209165

210-
expectation("error", msg, srcref, trace = x[["trace"]])
166+
new_expectation("error", msg, srcref = srcref, trace = x[["trace"]])
211167
}
212168

213169

@@ -217,12 +173,17 @@ is_simple_error <- function(x) {
217173

218174
#' @export
219175
as.expectation.warning <- function(x, srcref = NULL) {
220-
expectation("warning", cnd_message(x), srcref, trace = x[["trace"]])
176+
new_expectation(
177+
"warning",
178+
cnd_message(x),
179+
srcref = srcref,
180+
trace = x[["trace"]]
181+
)
221182
}
222183

223184
#' @export
224185
as.expectation.skip <- function(x, ..., srcref = NULL) {
225-
expectation("skip", cnd_message(x), srcref, trace = x[["trace"]])
186+
new_expectation("skip", cnd_message(x), srcref = srcref, trace = x[["trace"]])
226187
}
227188

228189
#' @export

β€ŽR/test-compiled-code.Rβ€Ž

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ run_cpp_tests <- function(package) {
6060
reporter$add_result(
6161
context = "Catch",
6262
test = "Catch",
63-
result = expectation("failure", e$message)
63+
result = new_expectation("failure", e$message)
6464
)
6565
reporter$end_test(context = "Catch", test = "Catch")
6666
}
@@ -89,7 +89,7 @@ run_cpp_tests <- function(package) {
8989
get_reporter()$start_test(context = context_name, test = test_name)
9090

9191
for (i in seq_len(successes)) {
92-
exp <- expectation("success", "")
92+
exp <- new_expectation("success", "")
9393
exp$test <- test_name
9494
get_reporter()$add_result(
9595
context = context_name,
@@ -122,7 +122,7 @@ run_cpp_tests <- function(package) {
122122
c(line, line, 1, 1)
123123
)
124124

125-
exp <- expectation("failure", org_text, srcref = failure_srcref)
125+
exp <- new_expectation("failure", org_text, srcref = failure_srcref)
126126
exp$test <- test_name
127127

128128
get_reporter()$add_result(
@@ -143,7 +143,7 @@ run_cpp_tests <- function(package) {
143143
c(line, line, 1, 1)
144144
)
145145

146-
exp <- expectation("error", exception_text, srcref = exception_srcref)
146+
exp <- new_expectation("error", exception_text, srcref = exception_srcref)
147147
exp$test <- test_name
148148

149149
get_reporter()$add_result(

β€ŽR/test-that.Rβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ test_code <- function(test, code, env, reporter, skip_on_empty = TRUE) {
141141
}
142142
handle_expectation <- function(e) {
143143
handled <<- TRUE
144-
register_expectation(e, 6)
144+
register_expectation(e, 7)
145145
invokeRestart("continue_test")
146146
}
147147
handle_warning <- function(e) {

β€Ž_pkgdown.ymlβ€Ž

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ reference:
7878
- title: Expectation internals
7979
contents:
8080
- expect
81-
- expectation
8281
- fail
8382
- expect_success
8483

β€Žman/expect.Rdβ€Ž

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

β€Žman/expectation.Rdβ€Ž

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

β€Žman/fail.Rdβ€Ž

Lines changed: 18 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
Β (0)