Skip to content

Commit 09f0141

Browse files
committed
Improve try_again()
* Simplify implemenation * Improve docs * First argument is now the number of retries, not tries Fixes #2050
1 parent 318a54b commit 09f0141

File tree

6 files changed

+76
-58
lines changed

6 files changed

+76
-58
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+
* `try_again()` is now publicised. The first argument is now the number of retries, not tries (#2050).
34
* `vignette("custom-expectations)` has been overhauled to make it much clearer how to create high-quality expectations (#2113, #2132, #2072).
45
* `expect_snapshot()` and friends will now fail when creating a new snapshot on CI. This is usually a signal that you've forgotten to run it locally before committing (#1461).
56
* `expect_snapshot_value()` can now handle expressions that generate `-` (#1678) or zero length atomic vectors (#2042).

R/try-again.R

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,38 @@
1-
#' Try evaluating an expressing multiple times until it succeeds.
1+
#' Try evaluating an expressing multiple times until it succeeds
22
#'
3-
#' @param times Maximum number of attempts.
4-
#' @param code Code to evaluate
5-
#' @keywords internal
3+
#' If you have a flaky test, you can use `try_again()` to run it a few times
4+
#' until it succeeds. In most cases, you are better fixing the underlying
5+
#' cause of the flakeyness, but sometimes that's not possible.
6+
#'
7+
#' @param times Number of times to retry.
8+
#' @param code Code to evaluate.
69
#' @export
710
#' @examples
8-
#' third_try <- local({
9-
#' i <- 3
10-
#' function() {
11-
#' i <<- i - 1
12-
#' if (i > 0) fail(paste0("i is ", i))
13-
#' }
14-
#' })
15-
#' try_again(3, third_try())
11+
#' usually_return_1 <- function(i) {
12+
#' if (runif(1) < 0.1) 0 else 1
13+
#' }
14+
#'
15+
#' \dontrun{
16+
#' # 10% chance of failure:
17+
#' expect_equal(usually_return_1(), 1)
18+
#'
19+
#' # 1% chance of failure:
20+
#' try_again(3, expect_equal(usually_return_1(), 1))
21+
#' }
1622
try_again <- function(times, code) {
17-
while (times > 0) {
18-
e <- tryCatch(
19-
withCallingHandlers(
20-
{
21-
code
22-
NULL
23-
},
24-
warning = function(e) {
25-
if (
26-
identical(e$message, "restarting interrupted promise evaluation")
27-
) {
28-
tryInvokeRestart("muffleWarning")
29-
}
30-
}
31-
),
32-
expectation_failure = function(e) {
33-
e
34-
},
35-
error = function(e) {
36-
e
37-
}
38-
)
23+
check_number_whole(times, min = 1)
3924

40-
if (is.null(e)) {
41-
return(invisible(TRUE))
42-
}
25+
code <- enquo(code)
4326

44-
times <- times - 1L
27+
i <- 1
28+
while (i <= times) {
29+
tryCatch(
30+
return(eval(get_expr(code), get_env(code))),
31+
expectation_failure = function(cnd) NULL
32+
)
33+
cli::cli_inform(c(i = "Expectation failed; trying again ({i})..."))
34+
i <- i + 1
4535
}
4636

47-
exp_signal(e)
37+
eval(get_expr(code), get_env(code))
4838
}

_pkgdown.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ reference:
4141
- expect_invisible
4242
- expect_output
4343
- expect_silent
44+
45+
- subtitle: Helpers
46+
contents:
47+
- try_again
4448
- local_reproducible_output
4549

4650
- title: Snapshot testing

man/try_again.Rd

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

tests/testthat/_snaps/try-again.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# tries multiple times
2+
3+
Code
4+
result <- try_again(3, third_try())
5+
Message
6+
i Expectation failed; trying again (1)...
7+
i Expectation failed; trying again (2)...
8+
9+
---
10+
11+
Code
12+
try_again(1, third_try())
13+
Message
14+
i Expectation failed; trying again (1)...
15+
Condition
16+
Error:
17+
! `i` (`actual`) is not equal to 0 (`expected`).
18+
19+
`actual`: 1.0
20+
`expected`: 0.0
21+

tests/testthat/test-try-again.R

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
succeed_after <- function(i) {
22
function() {
33
i <<- i - 1
4-
if (i > 0) {
5-
return(fail(paste0("i is ", i)))
6-
}
7-
pass(NULL)
4+
expect_equal(i, 0)
85
}
96
}
107

118
test_that("tries multiple times", {
129
third_try <- succeed_after(3)
13-
expect_true(try_again(3, third_try()))
10+
expect_snapshot(result <- try_again(3, third_try()))
11+
expect_equal(result, 0)
1412

1513
third_try <- succeed_after(3)
16-
expect_failure(try_again(2, third_try()), "i is 1")
14+
expect_snapshot(try_again(1, third_try()), error = TRUE)
1715
})

0 commit comments

Comments
 (0)