Skip to content

Commit a3189a8

Browse files
committed
Improved composition
`expect_named()` and `expect_output()` need to always return the input value, even if they use some subexpectation. To make this work, expectation components now only ever fail (never pass) and return TRUE or FALSE. Fixes `expect_named()` and `expect_output()` now return different outputs Fixes #2246
1 parent ed78cae commit a3189a8

File tree

10 files changed

+121
-71
lines changed

10 files changed

+121
-71
lines changed

R/expect-equality.R

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ expect_equal <- function(
6868
check_number_decimal(tolerance, min = 0, allow_null = TRUE)
6969

7070
if (edition_get() >= 3) {
71-
expect_waldo_equal_("equal", act, exp, info, ..., tolerance = tolerance)
71+
if (
72+
!expect_waldo_equal_("equal", act, exp, info, ..., tolerance = tolerance)
73+
) {
74+
return()
75+
}
7276
} else {
7377
if (!is.null(tolerance)) {
7478
comp <- compare(act$val, exp$val, ..., tolerance = tolerance)
@@ -84,8 +88,8 @@ expect_equal <- function(
8488
)
8589
return(fail(msg, info = info))
8690
}
87-
pass(act$val)
8891
}
92+
pass(act$val)
8993
}
9094

9195

@@ -103,30 +107,29 @@ expect_identical <- function(
103107
exp <- quasi_label(enquo(expected), expected.label)
104108

105109
if (edition_get() >= 3) {
106-
expect_waldo_equal_("identical", act, exp, info, ...)
110+
if (!expect_waldo_equal_("identical", act, exp, info, ...)) {
111+
return()
112+
}
107113
} else {
108114
ident <- identical(act$val, exp$val, ...)
109-
if (ident) {
110-
msg_act <- NULL
111-
} else {
115+
if (!ident) {
112116
compare <- compare(act$val, exp$val)
113117
if (compare$equal) {
114118
msg_act <- "Objects equal but not identical"
115119
} else {
116120
msg_act <- compare$message
117121
}
118-
}
119122

120-
if (!ident) {
121123
msg <- c(
122124
sprintf("Expected %s to be identical to %s.", act$lab, exp$lab),
123125
"Differences:",
124126
msg_act
125127
)
126128
return(fail(msg, info = info))
127129
}
128-
pass(act$val)
129130
}
131+
132+
pass(act$val)
130133
}
131134

132135
expect_waldo_equal_ <- function(
@@ -144,15 +147,16 @@ expect_waldo_equal_ <- function(
144147
x_arg = "actual",
145148
y_arg = "expected"
146149
)
147-
if (length(comp) != 0) {
148-
msg <- c(
149-
sprintf("Expected %s to be %s to %s.", act$lab, type, exp$lab),
150-
"Differences:",
151-
paste0(comp, collpase = "\n")
152-
)
153-
return(fail(msg, info = info, trace_env = trace_env))
150+
if (length(comp) == 0) {
151+
return(TRUE)
154152
}
155-
pass(act$val)
153+
154+
msg <- c(
155+
sprintf("Expected %s to be %s to %s.", act$lab, type, exp$lab),
156+
"Differences:",
157+
paste0(comp, collpase = "\n")
158+
)
159+
fail(msg, info = info, trace_env = trace_env)
156160
}
157161

158162
#' Is an object equal to the expected value, ignoring attributes?

R/expect-match.R

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,23 @@ expect_match <- function(
5050
return(fail(msg, info = info))
5151
}
5252

53-
expect_match_(
54-
act = act,
55-
regexp = regexp,
56-
perl = perl,
57-
fixed = fixed,
58-
...,
59-
all = all,
60-
info = info,
61-
label = label,
62-
negate = FALSE
63-
)
53+
if (
54+
!expect_match_(
55+
act = act,
56+
regexp = regexp,
57+
perl = perl,
58+
fixed = fixed,
59+
...,
60+
all = all,
61+
info = info,
62+
label = label,
63+
negate = FALSE
64+
)
65+
) {
66+
return()
67+
}
68+
69+
pass(act$val)
6470
}
6571

6672
#' @describeIn expect_match Check that a string doesn't match a regular
@@ -84,17 +90,22 @@ expect_no_match <- function(
8490
check_bool(fixed)
8591
check_bool(all)
8692

87-
expect_match_(
88-
act = act,
89-
regexp = regexp,
90-
perl = perl,
91-
fixed = fixed,
92-
...,
93-
all = all,
94-
info = info,
95-
label = label,
96-
negate = TRUE
97-
)
93+
if (
94+
!expect_match_(
95+
act = act,
96+
regexp = regexp,
97+
perl = perl,
98+
fixed = fixed,
99+
...,
100+
all = all,
101+
info = info,
102+
label = label,
103+
negate = TRUE
104+
)
105+
) {
106+
return()
107+
}
108+
pass(act$val)
98109
}
99110

100111
expect_match_ <- function(
@@ -115,7 +126,7 @@ expect_match_ <- function(
115126
ok <- if (all) all(condition) else any(condition)
116127

117128
if (ok) {
118-
return(pass(act$val))
129+
return(TRUE)
119130
}
120131

121132
values <- show_text(act$val, condition)
@@ -135,7 +146,7 @@ expect_match_ <- function(
135146
encodeString(regexp, quote = '"')
136147
)
137148
msg_act <- c(paste0("Actual ", title, ':'), values)
138-
return(fail(c(msg_exp, msg_act), info = info, trace_env = trace_env))
149+
fail(c(msg_exp, msg_act), info = info, trace_env = trace_env)
139150
}
140151

141152

R/expect-named.R

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,15 @@ expect_named <- function(
4747
act_names <- normalise_names(names(act$val), ignore.order, ignore.case)
4848

4949
if (ignore.order) {
50-
act <- labelled_value(act_names, paste0("names(", act$lab, ")"))
51-
return(expect_setequal_(act, exp))
50+
act_names <- labelled_value(act_names, paste0("names(", act$lab, ")"))
51+
if (!expect_setequal_(act_names, exp)) {
52+
return()
53+
}
5254
} else {
53-
act <- labelled_value(act_names, paste0("names(", act$lab, ")"))
54-
return(expect_waldo_equal_("equal", act, exp))
55+
act_name <- labelled_value(act_names, paste0("names(", act$lab, ")"))
56+
if (!expect_waldo_equal_("equal", act_name, exp)) {
57+
return()
58+
}
5559
}
5660

5761
pass(act$val)

R/expect-output.R

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,16 @@ expect_output <- function(
4242
)
4343
return(fail(msg, info = info))
4444
}
45-
pass(act$val)
4645
} else if (is.null(regexp) || identical(act$cap, "")) {
4746
if (identical(act$cap, "")) {
4847
msg <- sprintf("Expected %s to produce output.", act$lab)
4948
return(fail(msg, info = info))
5049
}
51-
pass(act$val)
5250
} else {
53-
act <- labelled_value(act$cap, act$lab)
54-
expect_match_(act, enc2native(regexp), ..., title = "output")
51+
act_out <- labelled_value(act$cap, paste0("output from ", act$lab))
52+
if (!expect_match_(act_out, enc2native(regexp), ..., title = "output")) {
53+
return()
54+
}
5555
}
56+
pass(act$val)
5657
}

R/expect-self-test.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ expect_failure <- function(expr, message = NULL, ...) {
8888

8989
if (!is.null(message)) {
9090
act <- labelled_value(status$last_failure$message, "failure message")
91-
return(expect_match_(act, message, ..., title = "message"))
91+
if (!expect_match_(act, message, ..., title = "message")) {
92+
return()
93+
}
9294
}
9395
pass(NULL)
9496
}

R/expect-setequal.R

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ expect_setequal <- function(object, expected) {
3434
testthat_warn("expect_setequal() ignores names")
3535
}
3636

37-
expect_setequal_(act, exp)
37+
if (!expect_setequal_(act, exp)) {
38+
return()
39+
}
40+
41+
pass(act$val)
3842
}
3943

4044
expect_setequal_ <- function(
@@ -45,22 +49,23 @@ expect_setequal_ <- function(
4549
act_miss <- unique(act$val[!act$val %in% exp$val])
4650
exp_miss <- unique(exp$val[!exp$val %in% act$val])
4751

48-
if (length(exp_miss) || length(act_miss)) {
49-
msg_exp <- sprintf(
50-
"Expected %s to have the same values as %s.",
51-
act$lab,
52-
exp$lab
53-
)
54-
msg_act <- c(
55-
sprintf("Actual: %s", values(act$val)),
56-
sprintf("Expected: %s", values(exp$val)),
57-
if (length(act_miss)) sprintf("Needs: %s", values(act_miss)),
58-
if (length(exp_miss)) sprintf("Absent: %s", values(exp_miss))
59-
)
60-
61-
return(fail(c(msg_exp, msg_act), trace_env = trace_env))
52+
if (length(exp_miss) == 0 && length(act_miss) == 0) {
53+
return(TRUE)
6254
}
63-
pass(act$val)
55+
56+
msg_exp <- sprintf(
57+
"Expected %s to have the same values as %s.",
58+
act$lab,
59+
exp$lab
60+
)
61+
msg_act <- c(
62+
sprintf("Actual: %s", values(act$val)),
63+
sprintf("Expected: %s", values(exp$val)),
64+
if (length(act_miss)) sprintf("Needs: %s", values(act_miss)),
65+
if (length(exp_miss)) sprintf("Absent: %s", values(exp_miss))
66+
)
67+
68+
fail(c(msg_exp, msg_act), trace_env = trace_env)
6469
}
6570

6671
values <- function(x) {
@@ -87,7 +92,11 @@ expect_mapequal <- function(object, expected) {
8792
act <- quasi_label(enquo(object))
8893
exp <- quasi_label(enquo(expected))
8994

90-
expect_waldo_equal_("equal", act, exp, list_as_map = TRUE)
95+
if (!expect_waldo_equal_("equal", act, exp, list_as_map = TRUE)) {
96+
return()
97+
}
98+
99+
pass(act$val)
91100
}
92101

93102
#' @export

R/expect-that.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
#' @param trace An optional backtrace created by [rlang::trace_back()].
2222
#' When supplied, the expectation is displayed with the backtrace.
2323
#' Expert use only.
24-
#' @export
24+
#' @return `pass()` returns `value` invisibly; `fail()` returns `FALSE`
25+
#' invisibly.
2526
#' @examples
2627
#' expect_length <- function(object, n) {
2728
#' act <- quasi_label(rlang::enquo(object), arg = "object")
@@ -44,6 +45,7 @@ fail <- function(
4445
trace <- trace %||% capture_trace(trace_env)
4546
message <- paste(c(message, info), collapse = "\n")
4647
expectation("failure", message, srcref = srcref, trace = trace)
48+
invisible(FALSE)
4749
}
4850

4951
snapshot_fail <- function(message, trace_env = caller_env()) {

tests/testthat/_snaps/expect-output.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
expect_output(g(), "x")
2323
Condition
2424
Error:
25-
! Expected `g()` to match regexp "x".
25+
! Expected output from `g()` to match regexp "x".
2626
Actual output:
2727
x | !
2828

tests/testthat/test-expect-named.R

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ test_that("expected_named verifies actual of names", {
1212
expect_snapshot_failure(expect_named(x, "b"))
1313
})
1414

15+
test_that("always returns inputs", {
16+
x <- c(a = 1)
17+
expect_equal(expect_named(x), x)
18+
expect_equal(expect_named(x, "a"), x)
19+
expect_equal(expect_named(x, "a", ignore.order = TRUE), x)
20+
})
21+
1522
test_that("expected_named optionally ignores order and case", {
1623
x <- c(a = 1, b = 2)
1724
expect_success(expect_named(x, c("A", "B"), ignore.case = TRUE))

tests/testthat/test-expect-output.R

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,18 @@ test_that("... passed on to grepl", {
3333
expect_success(expect_output(print("X"), "x", ignore.case = TRUE))
3434
})
3535

36-
test_that("returns first argument", {
37-
expect_equal(expect_output(1, NA), 1)
36+
test_that("always returns first argument", {
37+
f1 <- function() {
38+
1
39+
}
40+
f2 <- function() {
41+
cat("x")
42+
1
43+
}
44+
45+
expect_equal(expect_output(f1(), NA), 1)
46+
expect_equal(expect_output(f2()), 1)
47+
expect_equal(expect_output(f2(), "x"), 1)
3848
})
3949

4050
test_that("uses unicode characters in output where available", {

0 commit comments

Comments
 (0)