Skip to content

Commit 9495b1a

Browse files
committed
Merged origin/main into on-cran
2 parents 42a802a + f28b7b0 commit 9495b1a

Some content is hidden

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

55 files changed

+918
-581
lines changed

.Rbuildignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@
2323
^[\.]?air\.toml$
2424
^\.vscode$
2525
^\.git-blame-ignore-rev$
26+
^CLAUDE\.md$
27+
^\.claude$

.claude/settings.local.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(find:*)"
5+
],
6+
"deny": []
7+
}
8+
}

CLAUDE.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## About This Project
6+
7+
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.
8+
9+
## Key Development Commands
10+
11+
### Testing
12+
- `devtools::test()` or `Ctrl/Cmd+Shift+T` in RStudio - Run all tests
13+
- `devtools::test_file("tests/testthat/test-filename.R")` - Run tests in a specific file
14+
- `testthat::test_local()` - Run tests for local source package
15+
- `testthat::test_package("testthat")` - Run tests for installed package
16+
- `R CMD check` - Full package check including tests
17+
18+
### Building and Installation
19+
- `devtools::load_all()` or `Ctrl/Cmd+Shift+L` - Load package for development
20+
- `devtools::document()` - Generate documentation
21+
- `devtools::check()` - Run R CMD check
22+
- `devtools::install()` - Install package locally
23+
24+
## Core Architecture
25+
26+
### Main Components
27+
28+
1. **Core Testing Functions** (`R/test-that.R`, `R/test-package.R`):
29+
- `test_that()` - The fundamental testing function
30+
- `test_local()`, `test_package()`, `test_check()` - Different ways to run test suites
31+
32+
2. **Expectations** (`R/expect-*.R`):
33+
- Modular expectation functions (equality, conditions, types, etc.)
34+
- Each expectation type has its own file following the pattern `expect-[type].R`
35+
36+
3. **Reporters** (`R/reporter*.R`):
37+
- Different output formats for test results
38+
- Object-oriented design with base `Reporter` class
39+
- Includes check, debug, progress, summary, JUnit, TAP formats
40+
41+
4. **Snapshot Testing** (`R/snapshot*.R`):
42+
- Value snapshots, file snapshots, output snapshots
43+
- Automatic management and comparison of expected outputs
44+
45+
5. **Parallel Testing** (`R/parallel*.R`):
46+
- Multi-core test execution
47+
- Configuration via `Config/testthat/parallel: true` in DESCRIPTION
48+
49+
6. **Mocking** (`R/mock*.R`, `R/mock2*.R`):
50+
- Function mocking capabilities
51+
- Both legacy (`mock.R`) and modern (`mock2*.R`) implementations
52+
53+
### Key Design Patterns
54+
55+
- **Editions**: testthat has different "editions" with varying behavior, controlled by `Config/testthat/edition`
56+
- **Reporters**: Extensible reporting system using R6 classes
57+
- **Lazy Evaluation**: Expectations use substitute() and lazy evaluation for better error messages
58+
- **C++ Integration**: Core functionality implemented in C++ for performance
59+
60+
### File Organization
61+
62+
- `R/` - All R source code, organized by functionality
63+
- `src/` - C++ source code and Makevars
64+
- `inst/include/testthat/` - C++ headers for other packages to use
65+
- `tests/testthat/` - Package's own comprehensive test suite
66+
- `vignettes/` - Documentation on testing concepts and workflows
67+
68+
### Important Configuration
69+
70+
The package uses several DESCRIPTION fields for configuration:
71+
- `Config/testthat/edition: 3` - Sets testthat edition
72+
- `Config/testthat/parallel: true` - Enables parallel testing
73+
- `Config/testthat/start-first` - Tests to run first in parallel mode
74+
75+
### C++ Testing Infrastructure
76+
77+
testthat provides C++ testing capabilities via Catch framework:
78+
- Headers in `inst/include/testthat/`
79+
- Test runner infrastructure in `src/test-runner.cpp`
80+
- Integration with R's testing system
81+
82+
### Snapshot Testing Workflow
83+
84+
- Snapshots stored in `tests/testthat/_snaps/`
85+
- Different snapshot types: values, files, output
86+
- Version-specific snapshots for different R versions
87+
- Use `testthat::snapshot_accept()` to update snapshots
88+
89+
This codebase prioritizes backward compatibility, comprehensive testing, and clear, descriptive error messages to help R developers write better tests.

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
* New `local_on_cran(TRUE)` allows you to simulate how your tests will run on CRAN (#2112).
4+
* `expect_no_*()` now executes the entire code block, rather than stopping at the first message or warning (#1991).
5+
* `expect_no_failures()` and `expect_no_successes()` are now deprecated as `expect_success()` now test for no failures and `expect_failure()` tests for no successes (#)
46
* New `pass()` function to use in place of `succeed()` (#2113).
57
* `expectation()` is now a combination of `new_expectation()` and `exp_signal()` (#2125).
68
* `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).

R/expect-comparison.R

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@
1919
#' @name comparison-expectations
2020
NULL
2121

22-
expect_compare <- function(operator = c("<", "<=", ">", ">="), act, exp) {
22+
expect_compare_ <- function(
23+
operator = c("<", "<=", ">", ">="),
24+
act,
25+
exp,
26+
trace_env = caller_env()
27+
) {
2328
operator <- match.arg(operator)
2429
op <- match.fun(operator)
2530

@@ -34,26 +39,25 @@ expect_compare <- function(operator = c("<", "<=", ">", ">="), act, exp) {
3439
if (length(cmp) != 1 || !is.logical(cmp)) {
3540
abort("Result of comparison must be a single logical value")
3641
}
37-
expect(
38-
if (!is.na(cmp)) cmp else FALSE,
39-
sprintf(
42+
if (!isTRUE(cmp)) {
43+
msg <- sprintf(
4044
"%s is %s %s. Difference: %.3g",
4145
act$lab,
4246
msg,
4347
exp$lab,
4448
act$val - exp$val
45-
),
46-
trace_env = caller_env()
47-
)
48-
invisible(act$val)
49+
)
50+
return(fail(msg, trace_env = trace_env))
51+
}
52+
pass(act$val)
4953
}
5054
#' @export
5155
#' @rdname comparison-expectations
5256
expect_lt <- function(object, expected, label = NULL, expected.label = NULL) {
5357
act <- quasi_label(enquo(object), label, arg = "object")
5458
exp <- quasi_label(enquo(expected), expected.label, arg = "expected")
5559

56-
expect_compare("<", act, exp)
60+
expect_compare_("<", act, exp)
5761
}
5862

5963
#' @export
@@ -62,7 +66,7 @@ expect_lte <- function(object, expected, label = NULL, expected.label = NULL) {
6266
act <- quasi_label(enquo(object), label, arg = "object")
6367
exp <- quasi_label(enquo(expected), expected.label, arg = "expected")
6468

65-
expect_compare("<=", act, exp)
69+
expect_compare_("<=", act, exp)
6670
}
6771

6872
#' @export
@@ -71,7 +75,7 @@ expect_gt <- function(object, expected, label = NULL, expected.label = NULL) {
7175
act <- quasi_label(enquo(object), label, arg = "object")
7276
exp <- quasi_label(enquo(expected), expected.label, arg = "expected")
7377

74-
expect_compare(">", act, exp)
78+
expect_compare_(">", act, exp)
7579
}
7680

7781
#' @export
@@ -80,7 +84,7 @@ expect_gte <- function(object, expected, label = NULL, expected.label = NULL) {
8084
act <- quasi_label(enquo(object), label, arg = "object")
8185
exp <- quasi_label(enquo(expected), expected.label, arg = "expected")
8286

83-
expect_compare(">=", act, exp)
87+
expect_compare_(">=", act, exp)
8488
}
8589

8690

R/expect-condition.R

Lines changed: 62 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ expect_error <- function(
115115
label = NULL
116116
) {
117117
if (edition_get() >= 3) {
118-
expect_condition_matching(
118+
expect_condition_matching_(
119119
"error",
120120
{{ object }},
121121
regexp = regexp,
@@ -138,8 +138,10 @@ expect_error <- function(
138138

139139
# Access error fields with `[[` rather than `$` because the
140140
# `$.Throwable` from the rJava package throws with unknown fields
141-
expect(is.null(msg), msg, info = info, trace = act$cap[["trace"]])
142-
invisible(act$val %||% act$cap)
141+
if (!is.null(msg)) {
142+
return(fail(msg, info = info, trace = act$cap[["trace"]]))
143+
}
144+
pass(act$val %||% act$cap)
143145
}
144146
}
145147

@@ -161,7 +163,7 @@ expect_warning <- function(
161163
warn("The `all` argument is deprecated")
162164
}
163165

164-
expect_condition_matching(
166+
expect_condition_matching_(
165167
"warning",
166168
{{ object }},
167169
regexp = regexp,
@@ -186,9 +188,10 @@ expect_warning <- function(
186188
...,
187189
cond_type = "warnings"
188190
)
189-
expect(is.null(msg), msg, info = info)
190-
191-
invisible(act$val)
191+
if (!is.null(msg)) {
192+
return(fail(msg, info = info))
193+
}
194+
pass(act$val)
192195
}
193196
}
194197

@@ -205,7 +208,7 @@ expect_message <- function(
205208
label = NULL
206209
) {
207210
if (edition_get() >= 3) {
208-
expect_condition_matching(
211+
expect_condition_matching_(
209212
"message",
210213
{{ object }},
211214
regexp = regexp,
@@ -218,9 +221,10 @@ expect_message <- function(
218221
} else {
219222
act <- quasi_capture(enquo(object), label, capture_messages)
220223
msg <- compare_messages(act$cap, act$lab, regexp = regexp, all = all, ...)
221-
expect(is.null(msg), msg, info = info)
222-
223-
invisible(act$val)
224+
if (!is.null(msg)) {
225+
return(fail(msg, info = info))
226+
}
227+
pass(act$val)
224228
}
225229
}
226230

@@ -236,7 +240,7 @@ expect_condition <- function(
236240
label = NULL
237241
) {
238242
if (edition_get() >= 3) {
239-
expect_condition_matching(
243+
expect_condition_matching_(
240244
"condition",
241245
{{ object }},
242246
regexp = regexp,
@@ -262,13 +266,14 @@ expect_condition <- function(
262266
inherit = inherit,
263267
cond_type = "condition"
264268
)
265-
expect(is.null(msg), msg, info = info, trace = act$cap[["trace"]])
266-
267-
invisible(act$val %||% act$cap)
269+
if (!is.null(msg)) {
270+
return(fail(msg, info = info, trace = act$cap[["trace"]]))
271+
}
272+
pass(act$val %||% act$cap)
268273
}
269274
}
270275

271-
expect_condition_matching <- function(
276+
expect_condition_matching_ <- function(
272277
base_class,
273278
object,
274279
regexp = NULL,
@@ -280,6 +285,7 @@ expect_condition_matching <- function(
280285
trace_env = caller_env(),
281286
error_call = caller_env()
282287
) {
288+
check_condition_dots(regexp, ..., error_call = error_call)
283289
matcher <- cnd_matcher(
284290
base_class,
285291
class,
@@ -302,39 +308,32 @@ expect_condition_matching <- function(
302308

303309
# Access error fields with `[[` rather than `$` because the
304310
# `$.Throwable` from the rJava package throws with unknown fields
305-
expect(
306-
is.null(msg),
307-
msg,
308-
info = info,
309-
trace = act$cap[["trace"]],
310-
trace_env = trace_env
311-
)
312-
311+
if (!is.null(msg)) {
312+
return(fail(
313+
msg,
314+
info = info,
315+
trace = act$cap[["trace"]],
316+
trace_env = trace_env
317+
))
318+
}
313319
# If a condition was expected, return it. Otherwise return the value
314320
# of the expression.
315-
invisible(if (expected) act$cap else act$val)
321+
pass(if (expected) act$cap else act$val)
316322
}
317323

318324
# -------------------------------------------------------------------------
319325

320326
cnd_matcher <- function(
321327
base_class,
322328
class = NULL,
323-
pattern = NULL,
329+
regexp = NULL,
324330
...,
325331
inherit = TRUE,
326332
ignore_deprecation = FALSE,
327333
error_call = caller_env()
328334
) {
329335
check_string(class, allow_null = TRUE, call = error_call)
330-
check_string(pattern, allow_null = TRUE, allow_na = TRUE, call = error_call)
331-
332-
if (is.null(pattern) && dots_n(...) > 0) {
333-
cli::cli_abort(
334-
"Can't specify {.arg ...} without {.arg pattern}.",
335-
call = error_call
336-
)
337-
}
336+
check_string(regexp, allow_null = TRUE, allow_na = TRUE, call = error_call)
338337

339338
function(cnd) {
340339
if (!inherit) {
@@ -352,12 +351,12 @@ cnd_matcher <- function(
352351
if (!is.null(class) && !inherits(x, class)) {
353352
return(FALSE)
354353
}
355-
if (!is.null(pattern) && !isNA(pattern)) {
354+
if (!is.null(regexp) && !isNA(regexp)) {
356355
withCallingHandlers(
357-
grepl(pattern, conditionMessage(x), ...),
356+
grepl(regexp, conditionMessage(x), ...),
358357
error = function(e) {
359358
cli::cli_abort(
360-
"Failed to compare {base_class} to {.arg pattern}.",
359+
"Failed to compare {base_class} to {.arg regexp}.",
361360
parent = e,
362361
call = error_call
363362
)
@@ -582,3 +581,29 @@ cnd_message <- function(x) {
582581
withr::local_options(rlang_backtrace_on_error = "none")
583582
conditionMessage(x)
584583
}
584+
585+
check_condition_dots <- function(
586+
regexp = NULL,
587+
...,
588+
error_call = caller_env()
589+
) {
590+
if (!is.null(regexp) || missing(...)) {
591+
return()
592+
}
593+
594+
dot_names <- ...names()
595+
if (is.null(dot_names)) {
596+
dot_names <- rep("", ...length())
597+
}
598+
unnamed <- dot_names == ""
599+
dot_names[unnamed] <- paste0("..", seq_along(dot_names)[unnamed])
600+
601+
cli::cli_abort(
602+
c(
603+
"Can't supply {.arg ...} unless {.arg regexp} is set.",
604+
"*" = "Unused arguments: {.arg {dot_names}}.",
605+
i = "Did you mean to use {.arg regexp} so {.arg ...} is passed to {.fn grepl}?"
606+
),
607+
call = error_call
608+
)
609+
}

0 commit comments

Comments
 (0)