Skip to content

Commit 2a5c035

Browse files
committed
Merge commit '387a9fcde9e3391d80b9d1f669938c8200c818dc'
2 parents 4066f6c + 387a9fc commit 2a5c035

14 files changed

+243
-106
lines changed

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
"allow": [
55
"Bash(find:*)",
66
"Bash(R:*)",
7+
"Bash(rm:*)",
78
"Bash(air format:*)"
89
],
910
"deny": []
1011
}
11-
}
12+
}

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ General advice:
1616

1717
- `devtools::test()` - Run all tests
1818
- `devtools::test_file("tests/testthat/test-filename.R")` - Run tests in a specific file
19+
- DO NOT USE `devtools::test_active_file()`
1920
- `devtools::load_all()` - Load package for development
2021
- `devtools::document()` - Generate documentation
2122
- `devtools::check()` - Run R CMD check

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export(expect_no_warning)
114114
export(expect_null)
115115
export(expect_output)
116116
export(expect_output_file)
117+
export(expect_r6_class)
117118
export(expect_reference)
118119
export(expect_s3_class)
119120
export(expect_s4_class)

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
* `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 (#)
44
* `describe()` now correctly scopes `skip()` (#2007).
5+
* `ParallelProgressReporter` now respect `max_failures` (#1162).
6+
* The last snapshot is no longer lost if the snapshot file is missing the final newline (#2092). It's easy to accidentally remove this because there are two trailing new lines in snapshot files and many editors will automatically remove if you touch the file.
7+
* New `expect_r6_class()` (#2030).
58
* `expect_*()` functions consistently and rigorously check their inputs (#1754).
69
* `JunitReporter()` no longer fails with `"no applicable method for xml_add_child"` for warnings outside of tests (#1913). Additionally, warnings now save their backtraces.
710
* `JunitReporter()` strips ANSI escapes in more placese (#1852, #2032).

R/expect-inheritance.R

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,25 @@
1212
#' * `expect_s4_class(x, class)` checks that `x` is an S4 object that
1313
#' [is()] `class`.
1414
#' * `expect_s4_class(x, NA)` checks that `x` isn't an S4 object.
15+
#' * `expect_r6_class(x, class)` checks that `x` an R6 object that
16+
#' inherits from `class`.
1517
#' * `expect_s7_class(x, Class)` checks that `x` is an S7 object that
1618
#' [S7::S7_inherits()] from `Class`
1719
#'
1820
#' See [expect_vector()] for testing properties of objects created by vctrs.
1921
#'
2022
#' @param type String giving base type (as returned by [typeof()]).
21-
#' @param class
22-
#' * `expect_type()`: a single string giving an R base type.
23-
#' * `expect_s3_class()`: a character vector of class names or `NA` to assert
24-
#' that `object` isn't an S3 object. If you provide multiple class names,
25-
#' the test will pass if `object` inherits from any of them, unless
26-
#' `exact = TRUE`.
27-
#' * `expect_s4_class()`: a character vector of class names or `NA` to assert
28-
#' that `object` isn't an S4 object.
29-
#' * `expect_s7_class()`: an [S7::S7_class()] object.
23+
#' @param class The required type varies depending on the function:
24+
#' * `expect_type()`: a string.
25+
#' * `expect_s3_class()`: a string or character vector. The behaviour of
26+
#' multiple values (i.e. a character vector) is controlled by the
27+
#' `exact` argument.
28+
#' * `expect_s4_class()`: a string.
29+
#' * `expect_r6_class()`: a string.
30+
#' * `expect_s7_class()`: an [S7::S7_class()] object.
31+
#'
32+
#' For historical reasons, `expect_s3_class()` and `expect_s4_class()` also
33+
#' take `NA` to assert that the `object` is not an S3 or S4 object.
3034
#' @inheritParams expect_that
3135
#' @family expectations
3236
#' @examples
@@ -82,7 +86,7 @@ expect_type <- function(object, type) {
8286
#' @rdname inheritance-expectations
8387
#' @param exact If `FALSE`, the default, checks that `object` inherits
8488
#' from any element of `class`. If `TRUE`, checks that object has a class
85-
#' that's identical to `class`.
89+
#' that exactly matches `class`.
8690
expect_s3_class <- function(object, class, exact = FALSE) {
8791
check_bool(exact)
8892

@@ -154,6 +158,26 @@ expect_s4_class <- function(object, class) {
154158
pass(act$val)
155159
}
156160

161+
#' @export
162+
#' @rdname inheritance-expectations
163+
expect_r6_class <- function(object, class) {
164+
act <- quasi_label(enquo(object))
165+
check_string(class)
166+
167+
if (!inherits(act$val, "R6")) {
168+
return(fail(sprintf("%s is not an R6 object.", act$lab)))
169+
}
170+
171+
if (!inherits(act$val, class)) {
172+
act_class <- format_class(class(act$val))
173+
exp_class <- format_class(class)
174+
msg <- sprintf("%s inherits from %s not %s.", act$lab, act_class, exp_class)
175+
return(fail(msg))
176+
}
177+
178+
pass(act$val)
179+
}
180+
157181
#' @export
158182
#' @rdname inheritance-expectations
159183
expect_s7_class <- function(object, class) {

R/reporter-progress.R

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,16 +221,20 @@ ProgressReporter <- R6::R6Class(
221221
self$report_issues(self$ctxt_issues)
222222

223223
if (self$is_full()) {
224-
snapshotter <- get_snapshotter()
225-
if (!is.null(snapshotter)) {
226-
snapshotter$end_file()
227-
}
224+
self$report_full()
225+
}
226+
},
228227

229-
stop_reporter(c(
230-
"Maximum number of failures exceeded; quitting at end of file.",
231-
i = "Increase this number with (e.g.) {.run testthat::set_max_fails(Inf)}"
232-
))
228+
report_full = function() {
229+
snapshotter <- get_snapshotter()
230+
if (!is.null(snapshotter)) {
231+
snapshotter$end_file()
233232
}
233+
234+
stop_reporter(c(
235+
"Maximum number of failures exceeded; quitting at end of file.",
236+
i = "Increase this number with (e.g.) {.run testthat::set_max_fails(Inf)}"
237+
))
234238
},
235239

236240
add_result = function(context, test, result) {
@@ -258,6 +262,10 @@ ProgressReporter <- R6::R6Class(
258262
},
259263

260264
end_reporter = function() {
265+
if (self$is_full()) {
266+
return()
267+
}
268+
261269
self$cat_line()
262270

263271
colour_if <- function(n, type) {
@@ -465,7 +473,13 @@ ParallelProgressReporter <- R6::R6Class(
465473
self$report_issues(fsts$issues)
466474

467475
self$files[[self$file_name]] <- NULL
468-
if (length(self$files)) self$update(force = TRUE)
476+
if (length(self$files)) {
477+
self$update(force = TRUE)
478+
}
479+
480+
if (self$is_full()) {
481+
self$report_full()
482+
}
469483
},
470484

471485
end_reporter = function() {

R/snapshot-serialize.R

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ snap_from_md <- function(lines) {
1919
sep <- grepl("^-{3,}", lines)
2020
case_group <- cumsum(sep)
2121

22-
# Remove first line and last line, separator, line above and line below
22+
# Remove first line, separator, line above and line below
23+
# Only remove last line if it's empty (to handle missing final newlines)
2324
sep_loc <- which(sep)
24-
drop <- c(1, sep_loc, sep_loc + 1, sep_loc - 1, length(lines))
25+
drop <- c(1, sep_loc, sep_loc + 1, sep_loc - 1)
26+
if (length(lines) > 0 && lines[length(lines)] == "") {
27+
drop <- c(drop, length(lines))
28+
}
2529

2630
cases <- unname(split(lines[-drop], case_group[-drop]))
2731
code_unblock <- function(x) paste0(indent_del(x), collapse = "\n")

R/utils.R

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ rstudio_tickle <- function() {
3737
}
3838

3939
rstudioapi::executeCommand("vcsRefresh")
40-
rstudioapi::executeCommand("refreshFiles")
40+
if (!is_positron()) {
41+
rstudioapi::executeCommand("refreshFiles")
42+
}
43+
}
44+
45+
is_positron <- function() {
46+
nzchar(Sys.getenv("POSITRON", ""))
4147
}
4248

4349
first_upper <- function(x) {

man/inheritance-expectations.Rd

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

tests/testthat/_snaps/expect-inheritance.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@
4747

4848
`x` inherits from 'a'/'b' not 'c'/'d'.
4949

50+
# expect_r6_class generates useful failures
51+
52+
`x` is not an R6 object.
53+
54+
# expect_r6_class validates its inputs
55+
56+
Code
57+
expect_r6_class(1, c("Person", "Student"))
58+
Condition
59+
Error in `expect_r6_class()`:
60+
! `class` must be a single string, not a character vector.
61+
5062
# can check with actual class
5163

5264
Foo() inherits from <Foo> not <Bar>.

0 commit comments

Comments
 (0)