Skip to content

Commit d801d65

Browse files
committed
Merge branch 'main' into fix/parallel-teardown
2 parents f24641d + c80ca1a commit d801d65

File tree

162 files changed

+12030
-3239
lines changed

Some content is hidden

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

162 files changed

+12030
-3239
lines changed

.Rbuildignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@
2525
^\.git-blame-ignore-rev$
2626
^CLAUDE\.md$
2727
^\.claude$
28+
^vignettes/articles$

.github/workflows/claude-code-review.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Claude Code Review
22

33
on:
44
pull_request:
5-
types: [opened, synchronize]
5+
types: [opened]
66
# Optional: Only run on specific file changes
77
# paths:
88
# - "src/**/*.ts"
@@ -48,7 +48,7 @@ jobs:
4848
to add, just reply LGTM.
4949
5050
# Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
51-
use_sticky_comment: true
51+
# use_sticky_comment: true
5252

5353
# Optional: Add specific tools for running tests or linting
5454
# allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)"

CLAUDE.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,30 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
66

77
testthat is R's most popular unit testing framework, used by thousands of CRAN packages. It provides functions to make testing R code as fun and addictive as possible, with clear expectations, visual progress indicators, and seamless integration with R package development workflows.
88

9-
## Key Development Commands
9+
## Key development commands
1010

1111
General advice:
1212
* When running R from the console, always run it with `--quiet --vanilla`
1313
* Always run `air format .` after generating code
1414

15-
### Development tools
15+
### Testing
1616

17-
- `devtools::test()` - Run all tests
18-
- `devtools::test_file("tests/testthat/test-filename.R")` - Run tests in a specific file
17+
- Use `devtools::test()` to run all tests
18+
- Use `devtools::test_file("tests/testthat/test-filename.R")` to run tests in a specific file
1919
- DO NOT USE `devtools::test_active_file()`
20-
- `devtools::load_all()` - Load package for development
21-
- `devtools::check()` - Run R CMD check
22-
- `devtools::install()` - Install package locally
20+
- All testing functions automatically load code; you don't needs to.
21+
22+
- All new code should have an accompanying test.
23+
- Tests for `R/{name}.R` go in `tests/testthat/test-{name}.R`.
24+
- If there are existing tests, place new tests next to similar existing tests.
2325

2426
### Documentation
2527

26-
- Always run `devtools::document()` after changing any roxygen2 docs.
28+
- Run `devtools::document()` after changing any roxygen2 docs.
29+
- Every user facing function should be exported and have roxygen2 documentation.
30+
- Whenever you add a new documentation file, make sure to also add the topic name to `_pkgdown.yml`.
31+
- Run `pkgdown::check_pkgdown()` to check that all topics are included in the reference index.
32+
- Use sentence case for all headings
2733

2834
## Core Architecture
2935

DESCRIPTION

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Suggests:
4040
curl (>= 0.9.5),
4141
diffviewer (>= 0.1.0),
4242
digest (>= 0.6.33),
43+
gh,
4344
knitr,
4445
rmarkdown,
4546
rstudioapi,
@@ -56,4 +57,4 @@ Config/testthat/parallel: true
5657
Config/testthat/start-first: watcher, parallel*
5758
Encoding: UTF-8
5859
Roxygen: list(markdown = TRUE, r6 = FALSE)
59-
RoxygenNote: 7.3.2
60+
RoxygenNote: 7.3.3

NAMESPACE

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ S3method(print,comparison)
2424
S3method(print,expectation)
2525
S3method(print,mismatch_character)
2626
S3method(print,mismatch_numeric)
27+
S3method(print,testthat_hint)
2728
S3method(print,testthat_results)
2829
S3method(snapshot_replay,character)
2930
S3method(snapshot_replay,condition)
@@ -78,9 +79,13 @@ export(equals_reference)
7879
export(evaluate_promise)
7980
export(exp_signal)
8081
export(expect)
82+
export(expect_all_equal)
83+
export(expect_all_false)
84+
export(expect_all_true)
8185
export(expect_condition)
8286
export(expect_contains)
8387
export(expect_cpp_tests_pass)
88+
export(expect_disjoint)
8489
export(expect_equal)
8590
export(expect_equal_to_reference)
8691
export(expect_equivalent)
@@ -158,6 +163,9 @@ export(it)
158163
export(local_edition)
159164
export(local_mock)
160165
export(local_mocked_bindings)
166+
export(local_mocked_r6_class)
167+
export(local_mocked_s3_method)
168+
export(local_mocked_s4_method)
161169
export(local_on_cran)
162170
export(local_reproducible_output)
163171
export(local_snapshotter)
@@ -192,6 +200,7 @@ export(skip_on_os)
192200
export(skip_on_travis)
193201
export(skip_unless_r)
194202
export(snapshot_accept)
203+
export(snapshot_download_gh)
195204
export(snapshot_reject)
196205
export(snapshot_review)
197206
export(source_dir)
@@ -233,4 +242,5 @@ importFrom(brio,readLines)
233242
importFrom(brio,writeLines)
234243
importFrom(lifecycle,deprecated)
235244
importFrom(magrittr,"%>%")
245+
importFrom(methods,new)
236246
useDynLib(testthat, .registration = TRUE)

NEWS.md

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

3+
* New `expect_disjoint()` to check for the absence of values (@stibu81, #1851).
4+
* `expect_all_equal()`, `expect_all_true()`, and `expect_all_false()` are a new family of expectations that checks that every element of a vector has the same value. Compared to using `expect_true(all(...))` they give better failure messages (#1836, #2235).
5+
* Expectations now consistently return the value of the first argument, regardless of whether the expectation succeeds or fails. The primary exception are `expect_message()` and friends which will return the condition. This shouldn't affect existing tests, but will make failures clearer when you chain together multiple expectations (#2246).
6+
* `set_state_inspector()` gains `tolerance` argument and ignores minor FP differences by default (@mcol, #2237).
7+
* `expect_vector()` fails, instead of erroring, if `object` is not a vector (@plietar, #2224).
8+
* New `vignette("mocking")` explains mocking in detail (#1265).
9+
* New `vignette("challenging-functions")` provides an index to other documentation organised by testing challenges (#1265).
10+
* When running a test interactively, testthat now reports the number of successes. The results should also be more useful if you are using nested tests.
11+
* The hints generated by `expect_snapshot()` and `expect_snapshot_file()` now include the path to the package, if it's not in the current working directory (#1577).
12+
* `expect_snapshot_file()` now clearly errors if the `path` doesn't exist (#2191).
13+
* `expect_snapshot_file()` now considers `.json` to be a text file (#1593).
14+
* `expect_snapshot_file()` now shows differences for text files (#1593).
15+
* The failure messages for all `expect_` functions have been rewritten to first state what was expected and then what was actually received (#2142).
16+
* `test_file(desc = ...)` no longer loses snapshot results (#2066).
17+
* In `R CMD check`, snapshots now only advise on how to resolve failures once (#2207).
18+
* `snapshot_review()` includes a reject button and only displays the file navigation and the skip button if there are multiple files to review (#2025).
19+
* New `snapshot_download_gh()` makes it easy to get snapshots off GitHub and into your local package (#1779).
20+
* New `local_mocked_s3_method()`, `local_mocked_s4_method()`, and `local_mocked_r6_class()` allow you to mock S3 and S4 methods and R6 classes (#1892, #1916)
321
* `expect_snapshot_file(name=)` must have a unique file path. If a snapshot file attempts to be saved with a duplicate `name`, an error will be thrown. (#1592)
4-
* `test_dir()`, `test_file()`, `test_package()`, `test_check()`, `test_local()`, `source_file()` gain a `shuffle` argument uses `sample()` to randomly reorder the top-level expressions in each test file (#1942). This random reordering surfaces dependencies between tests and code outside of any test, as well as dependencies between tests. This helps you find and eliminate unintentional dependencies.
22+
* `test_dir()`, `test_file()`, `test_package()`, `test_check()`, `test_local()`, `source_file()` gain a `shuffle` argument that uses `sample()` to randomly reorder the top-level expressions in each test file (#1942). This random reordering surfaces dependencies between tests and code outside of any test, as well as dependencies between tests. This helps you find and eliminate unintentional dependencies.
523
* `snapshot_accept(test)` now works when the test file name contains `.` (#1669).
624
* `local_mock()` and `with_mock()` have been deprecated because they are no longer permitted in R 4.5.
725
* `snapshot_review()` now passes `...` on to `shiny::runApp()` (#1928).
826
* `expect_named()` now gives more informative errors (#2091).
9-
* `expect_*()` functions consistently and rigorously check their inputs (#1754).
27+
* `expect_*()` functions consistently and rigorously check their inputs (#1754).
1028
* `test_that()` no longer warns about the absence of `{}` since it no longer seems to be necessary.
1129
* `test_that()`, `describe()`, and `it()` can now be arbitrarily nested. Each component will skip only if it and its subtests don't contain any expectations. The interactive stop reporter has been fixed so it doesn't duplicate failures. (#2063, #2188).
1230
* Test filtering now works with `it()`, and the `desc` argument can take a character vector in order to recursively filter subtests (i.e. `it()` nested inside of `describe()`) (#2118).
1331
* New `snapshot_reject()` rejects all modified snapshots by deleting the `.new` variants (#1923).
1432
* New `SlowReporter` makes it easier to find the slowest tests in your package. The easiest way to run it is with `devtools::test(reporter = "slow")` (#1466).
15-
* Power `expect_mapequal()` with `waldo::compare(list_as_map = TRUE)` (#1521).
1633
* On CRAN, `test_that()` now automatically skips if a package is not installed (#1585). Practically, this means that you no longer need to check that suggested packages are installed. (We don't do this in the tidyverse because we think it has limited payoff, but other styles advise differently.)
1734
* `expect_snapshot()` no longer skips on CRAN, as that skips the rest of the test. Instead it just returns, neither succeeding nor failing (#1585).
18-
* Interrupting a test now prints the test name. This makes it easier to tell where a very slow test might be hanging (#1464)
35+
* Interrupting a test now prints the test name. This makes it easier to tell where a very slow test might be hanging (#1464).
1936
* Parallel testthat now does not ignore test files with syntax errors (#1360).
2037
* `expect_lt()`, `expect_gt()`, and friends have a refined display that is more likely to display the correct number of digits and shows you the actual values compared.
2138
* `describe()`, `it()`, and `test_that()` now have a shared stack of descriptions so that if you nest any inside of each other, any resulting failures will show you the full path.
@@ -25,9 +42,9 @@
2542
* New `expect_r6_class()` (#2030).
2643
* `expect_*()` functions consistently and rigorously check their inputs (#1754).
2744
* `JunitReporter()` no longer fails with `"no applicable method for xml_add_child"` for warnings outside of tests (#1913). Additionally, warnings now save their backtraces.
28-
* `JunitReporter()` strips ANSI escapes in more placese (#1852, #2032).
45+
* `JunitReporter()` strips ANSI escapes in more places (#1852, #2032).
2946
* `try_again()` is now publicised. The first argument is now the number of retries, not tries (#2050).
30-
* `vignette("custom-expectations)` has been overhauled to make it much clearer how to create high-quality expectations (#2113, #2132, #2072).
47+
* `vignette("custom-expectations")` has been overhauled to make it much clearer how to create high-quality expectations (#2113, #2132, #2072).
3148
* `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).
3249
* `expect_snapshot_value()` can now handle expressions that generate `-` (#1678) or zero length atomic vectors (#2042).
3350
* `expect_matches()` failures should be a little easier to read (#2135, #2181).
@@ -974,7 +991,7 @@ This release mostly focusses on an overhaul of how testthat works with condition
974991
* In `expect_equal_to_reference()`, the default value for `update` is
975992
now `FALSE` (@BrodieG, #683).
976993

977-
* `expect_error()` now returns the error object as documentated (#724).
994+
* `expect_error()` now returns the error object as documented (#724).
978995
It also now warns if you're using a classed expectation and you're
979996
not using the `class` argument. This is good practice as it decouples the
980997
error object (which tends to be stable) from its rendering to the user
@@ -1666,7 +1683,7 @@ The reporters system class has been considerably refactored to make existing rep
16661683
* `safe_digest()` uses a better strategy, and returns NA for directories
16671684
(#138, #146).
16681685

1669-
* Random praise is renabled by default (again!) (#164).
1686+
* Random praise is re-enabled by default (again!) (#164).
16701687

16711688
* Teamcity reporter now correctly escapes output messages (#150, @windelinckx).
16721689
It also uses nested suites to include test names.

R/expect-all.R

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#' Do you expect every value in a vector to have this value?
2+
#'
3+
#' These expectations are similar to `expect_true(all(x == "x"))`,
4+
#' `expect_true(all(x))` and `expect_true(all(!x))` but give more informative
5+
#' failure messages if the expectations are not met.
6+
#'
7+
#' @inheritParams expect_equal
8+
#' @export
9+
#' @examples
10+
#' x1 <- c(1, 1, 1, 1, 1, 1)
11+
#' expect_all_equal(x1, 1)
12+
#'
13+
#' x2 <- c(1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2)
14+
#' show_failure(expect_all_equal(x2, 1))
15+
#'
16+
#' # expect_all_true() and expect_all_false() are helpers for common cases
17+
#' set.seed(1016)
18+
#' show_failure(expect_all_true(rpois(100, 10) < 20))
19+
#' show_failure(expect_all_false(rpois(100, 10) > 20))
20+
expect_all_equal <- function(object, expected) {
21+
act <- quasi_label(enquo(object))
22+
exp <- quasi_label(enquo(expected))
23+
24+
expect_all_equal_(act, exp)
25+
invisible(act$val)
26+
}
27+
28+
#' @export
29+
#' @rdname expect_all_equal
30+
expect_all_true <- function(object) {
31+
act <- quasi_label(enquo(object))
32+
exp <- labelled_value(TRUE, "TRUE")
33+
34+
expect_all_equal_(act, exp)
35+
invisible(act$val)
36+
}
37+
38+
#' @export
39+
#' @rdname expect_all_equal
40+
expect_all_false <- function(object) {
41+
act <- quasi_label(enquo(object))
42+
exp <- labelled_value(FALSE, "FALSE")
43+
44+
expect_all_equal_(act, exp)
45+
invisible(act$val)
46+
}
47+
48+
49+
expect_all_equal_ <- function(act, exp, trace_env = caller_env()) {
50+
check_vector(act$val, error_call = trace_env, error_arg = "object")
51+
if (length(act$val) == 0) {
52+
cli::cli_abort("{.arg object} must not be empty.", call = trace_env)
53+
}
54+
55+
check_vector(exp$val, error_call = trace_env, error_arg = "expected")
56+
if (length(exp$val) != 1) {
57+
cli::cli_abort("{.arg expected} must be length 1.", call = trace_env)
58+
}
59+
60+
exp$val <- rep(exp$val, length(act$val))
61+
names(exp$val) <- names(act$val)
62+
expect_waldo_equal_(
63+
"Expected every element of %s to equal %s.",
64+
act,
65+
exp,
66+
tolerance = testthat_tolerance(),
67+
trace_env = trace_env
68+
)
69+
}

R/expect-comparison.R

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,49 +28,60 @@ expect_compare_ <- function(
2828
operator <- match.arg(operator)
2929
op <- match.fun(operator)
3030

31-
msg <- c(
32-
"<" = "not strictly less than",
33-
"<=" = "not less than",
34-
">" = "not strictly greater than",
35-
">=" = "not greater than"
36-
)[[operator]]
37-
38-
negated_op <- switch(operator, "<" = ">=", "<=" = ">", ">" = "<=", ">=" = "<")
39-
4031
cmp <- op(act$val, exp$val)
4132
if (length(cmp) != 1 || !is.logical(cmp)) {
4233
cli::cli_abort(
43-
"Result of comparison must be a single logical value.",
34+
"Result of comparison must be `TRUE`, `FALSE`, or `NA`",
4435
call = trace_env
4536
)
37+
} else if (!isTRUE(cmp)) {
38+
msg <- failure_compare(act, exp, operator)
39+
fail(msg, trace_env = trace_env)
40+
} else {
41+
pass()
4642
}
47-
if (!isTRUE(cmp)) {
48-
digits <- max(
49-
digits(act$val),
50-
digits(exp$val),
51-
min_digits(act$val, exp$val)
52-
)
53-
msg <- sprintf(
54-
"%s is %s %s.\n%s - %s = %s %s 0",
55-
act$lab,
56-
msg,
57-
exp$lab,
58-
num_exact(act$val, digits),
59-
num_exact(exp$val, digits),
60-
num_exact(act$val - exp$val, digits),
61-
negated_op
43+
}
44+
45+
failure_compare <- function(act, exp, operator) {
46+
actual_op <- switch(operator, "<" = ">=", "<=" = ">", ">" = "<=", ">=" = "<")
47+
48+
diff <- act$val - exp$val
49+
msg_exp <- sprintf("Expected %s %s %s.", act$lab, operator, exp$lab)
50+
51+
digits <- max(
52+
digits(act$val),
53+
digits(exp$val),
54+
min_digits(act$val, exp$val)
55+
)
56+
57+
msg_act <- sprintf(
58+
"Actual comparison: %s %s %s",
59+
num_exact(act$val, digits),
60+
actual_op,
61+
num_exact(exp$val, digits)
62+
)
63+
64+
if (is.na(diff)) {
65+
msg_diff <- NULL
66+
} else {
67+
msg_diff <- sprintf(
68+
"Difference: %s %s 0",
69+
num_exact(diff, digits),
70+
actual_op
6271
)
63-
return(fail(msg, trace_env = trace_env))
6472
}
65-
pass(act$val)
73+
74+
c(msg_exp, msg_act, msg_diff)
6675
}
76+
6777
#' @export
6878
#' @rdname comparison-expectations
6979
expect_lt <- function(object, expected, label = NULL, expected.label = NULL) {
7080
act <- quasi_label(enquo(object), label)
7181
exp <- quasi_label(enquo(expected), expected.label)
7282

7383
expect_compare_("<", act, exp)
84+
invisible(act$val)
7485
}
7586

7687
#' @export
@@ -80,6 +91,7 @@ expect_lte <- function(object, expected, label = NULL, expected.label = NULL) {
8091
exp <- quasi_label(enquo(expected), expected.label)
8192

8293
expect_compare_("<=", act, exp)
94+
invisible(act$val)
8395
}
8496

8597
#' @export
@@ -89,6 +101,7 @@ expect_gt <- function(object, expected, label = NULL, expected.label = NULL) {
89101
exp <- quasi_label(enquo(expected), expected.label)
90102

91103
expect_compare_(">", act, exp)
104+
invisible(act$val)
92105
}
93106

94107
#' @export
@@ -98,6 +111,7 @@ expect_gte <- function(object, expected, label = NULL, expected.label = NULL) {
98111
exp <- quasi_label(enquo(expected), expected.label)
99112

100113
expect_compare_(">=", act, exp)
114+
invisible(act$val)
101115
}
102116

103117

0 commit comments

Comments
 (0)