Skip to content

Commit 7698c6c

Browse files
committed
Improve self-testing capabilities
`expect_success()` and `expect_failure()` are now stricter, testing the full set of assumptions (i.e. exactly one success/failure and zero failures/successes). This means that `expect_no_successes()` and `expect_no_failures()` can be deprecated. Fixes #2110
1 parent acbf9d6 commit 7698c6c

File tree

12 files changed

+211
-103
lines changed

12 files changed

+211
-103
lines changed

NEWS.md

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

3+
* `expect_no_failures()` and `expect_no_succeses()` are now deprecated as `expect_success()` now test for no failures and `expect_failure()` tests for no successes (#)
34
* `local_edition()` now gives a useful error for bad values (#1547).
45
* testthat now requires R 4.1.
56
* `expect_s4_class()` now supports unquoting (@stibu81, #2064).

R/expect-self-test.R

Lines changed: 76 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,39 @@
1-
capture_failure <- new_capture("expectation_failure")
2-
capture_success <- function(expr) {
1+
capture_success_failure <- function(expr) {
32
cnd <- NULL
43

5-
withCallingHandlers(
6-
expr,
7-
expectation_failure = function(cnd) {
8-
invokeRestart("continue_test")
9-
},
10-
expectation_success = function(cnd) {
11-
cnd <<- cnd
12-
}
4+
n_success <- 0
5+
n_failure <- 0
6+
7+
last_failure <- NULL
8+
9+
withRestarts(
10+
withCallingHandlers(
11+
expr,
12+
expectation_failure = function(cnd) {
13+
last_failure <<- cnd
14+
n_failure <<- n_failure + 1
15+
# Finish the test without bubbling up
16+
invokeRestart("failed")
17+
},
18+
expectation_success = function(cnd) {
19+
n_success <<- n_success + 1
20+
# Don't bubble up to any other handlers
21+
invokeRestart("continue_test")
22+
}
23+
),
24+
failed = function() {}
1325
)
14-
cnd
15-
}
1626

17-
new_capture("expectation_success")
27+
list(n_success = n_success, n_failure = n_failure, last_failure = last_failure)
28+
}
1829

1930
#' Tools for testing expectations
2031
#'
2132
#' @description
22-
#' * `expect_success()` and `expect_failure()` check that there's at least
23-
#' one success or failure respectively.
24-
#' * `expect_snapshot_failure()` records the failure message so that you can
25-
#' manually check that it is informative.
26-
#' * `expect_no_success()` and `expect_no_failure()` check that are no
27-
#' successes or failures.
33+
#' `expect_success()` checks that there's exactly one success and no failures;
34+
#' `expect_failure()` checks that there's exactly one failure and no successes.
35+
#' `expect_snapshot_failure()` records the failure message so that you can
36+
#' manually check that it is informative.
2837
#'
2938
#' Use `show_failure()` in examples to print the failure message without
3039
#' throwing an error.
@@ -34,41 +43,40 @@ new_capture("expectation_success")
3443
#' @param ... Other arguments passed on to [expect_match()].
3544
#' @export
3645
expect_success <- function(expr) {
37-
exp <- capture_success(expr)
46+
status <- capture_success_failure(expr)
3847

39-
if (is.null(exp)) {
40-
fail("Expectation did not succeed")
41-
} else {
48+
if (status$n_success == 1 && status$n_failure == 0) {
4249
succeed()
50+
} else if (status$n_success == 0) {
51+
fail("Expectation did not succeed")
52+
} else if (status$n_success > 1) {
53+
fail(sprintf("Expectation succeeded %i times, instead of once", status$n_success))
54+
} else if (status$n_failure > 0) {
55+
fail(sprintf("Expectation failed %i times, instead of zero", status$n_failure))
4356
}
44-
invisible(NULL)
45-
}
4657

47-
#' @export
48-
#' @rdname expect_success
49-
expect_no_success <- function(expr) {
50-
exp <- capture_success(expr)
51-
52-
if (!is.null(exp)) {
53-
fail("Expectation succeeded")
54-
} else {
55-
succeed()
56-
}
5758
invisible(NULL)
5859
}
5960

6061
#' @export
6162
#' @rdname expect_success
6263
expect_failure <- function(expr, message = NULL, ...) {
63-
exp <- capture_failure(expr)
64+
status <- capture_success_failure(expr)
6465

65-
if (is.null(exp)) {
66+
if (status$n_failure == 1 && status$n_success == 0) {
67+
if (!is.null(message)) {
68+
return(expect_match(status$last_failure$message, message, ...))
69+
}
70+
} else if (status$n_failure == 0) {
6671
fail("Expectation did not fail")
67-
} else if (!is.null(message)) {
68-
expect_match(exp$message, message, ...)
69-
} else {
70-
succeed()
72+
} else if (status$n_failure > 1) {
73+
# This should be impossible, but including for completeness
74+
fail("Expectation failed more than once")
75+
} else if (status$n_success != 0) {
76+
fail(sprintf("Expectation succeeded %i times, instead of never", status$n_success))
7177
}
78+
79+
succeed()
7280
invisible(NULL)
7381
}
7482

@@ -78,12 +86,36 @@ expect_snapshot_failure <- function(expr) {
7886
expect_snapshot_error(expr, "expectation_failure")
7987
}
8088

89+
#' Test for absence of success or failure
90+
#'
91+
#' @description
92+
#' `r lifecycle::badge("deprecated")`
93+
#'
94+
#' These functions are deprecated because [expect_success()] and
95+
#' [expect_failure()] now test for exactly one success or no failures, and
96+
#' exactly one failure and no successes.
97+
#'
98+
#' @keywords internal
8199
#' @export
82-
#' @rdname expect_success
100+
expect_no_success <- function(expr) {
101+
lifecycle::deprecate_warn("3.3.0", "expect_no_success()", "expect_failure()")
102+
status <- capture_success_failure(expr)
103+
104+
if (status$n_success > 0) {
105+
fail("Expectation succeeded")
106+
} else {
107+
succeed()
108+
}
109+
invisible(NULL)
110+
}
111+
112+
#' @export
113+
#' @rdname expect_no_success
83114
expect_no_failure <- function(expr) {
84-
exp <- capture_failure(expr)
115+
lifecycle::deprecate_warn("3.3.0", "expect_no_failure()", "expect_success()")
116+
status <- capture_success_failure(expr)
85117

86-
if (!is.null(exp)) {
118+
if (status$n_failure > 0) {
87119
fail("Expectation failed")
88120
} else {
89121
succeed()

R/try-again.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,5 @@ try_again <- function(times, code) {
4242
times <- times - 1L
4343
}
4444

45-
stop(e)
45+
exp_signal(e)
4646
}

man/expect_no_success.Rd

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

man/expect_success.Rd

Lines changed: 3 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# errors in expect_success bubble up
2+
3+
Code
4+
expect_success(abort("error"))
5+
Condition
6+
Error:
7+
! error
8+
9+
# expect_no are deprecated
10+
11+
Code
12+
expect_no_failure(succeed())
13+
Condition
14+
Warning:
15+
`expect_no_failure()` was deprecated in testthat 3.3.0.
16+
i Please use `expect_success()` instead.
17+
Code
18+
expect_no_success(fail())
19+
Condition
20+
Warning:
21+
`expect_no_success()` was deprecated in testthat 3.3.0.
22+
i Please use `expect_failure()` instead.
23+

tests/testthat/test-expect-known.R

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@ test_that("uses specified width", {
1212

1313
test_that("creates file on first run", {
1414
file <- withr::local_tempfile()
15-
expect_success(
16-
expect_warning(
17-
expect_known_output(cat("ok!\n"), file),
18-
"Creating reference"
19-
)
20-
)
15+
expect_known_output(cat("ok!\n"), file) |>
16+
expect_success() |>
17+
expect_warning("Creating reference")
2118

2219
expect_true(file.exists(file))
2320
})
@@ -77,12 +74,9 @@ test_that("correctly matches to a file", {
7774
})
7875

7976
test_that("first run is successful", {
80-
expect_success(
81-
expect_warning(
82-
expect_known_value(2, "two.rds"),
83-
"Creating reference"
84-
)
85-
)
77+
expect_known_value(2, "two.rds") |>
78+
expect_success() |>
79+
expect_warning("Creating reference")
8680
unlink("two.rds")
8781
})
8882

@@ -131,12 +125,9 @@ test_that("version 3 is possible", {
131125
# expect_known_hash -------------------------------------------------------
132126

133127
test_that("empty hash succeeds with warning", {
134-
expect_success(
135-
expect_warning(
136-
expect_known_hash(1:10),
137-
"No recorded hash"
138-
)
139-
)
128+
expect_known_hash(1:10) |>
129+
expect_success() |>
130+
expect_warning("No recorded hash")
140131
})
141132

142133
test_that("only succeeds if hash is correct", {

tests/testthat/test-expect-no-condition.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ test_that("expect_no_* pass with pure code", {
2222
})
2323

2424
test_that("expect_no_* don't emit success when they fail", {
25-
expect_no_success(expect_no_error(stop("!")))
25+
expect_failure(expect_no_error(stop("!")))
2626
})
2727

2828
test_that("capture correct trace_env (#1994)", {

0 commit comments

Comments
 (0)