From ae4c8aa1a493d0d86de0b630fececd8ebfe608eb Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Mon, 6 Oct 2025 17:10:59 -0500 Subject: [PATCH 01/11] WIP on `expect_all_equal()` Fixes #1836. Fixes #2235 --- R/expect-all.R | 20 ++++++++++ R/expect-match.R | 21 +++++----- tests/testthat/_snaps/expect-all.md | 56 +++++++++++++++++++++++++++ tests/testthat/_snaps/expect-match.md | 20 +++------- tests/testthat/test-expect-all.R | 26 +++++++++++++ tests/testthat/test-expect-match.R | 7 ---- 6 files changed, 118 insertions(+), 32 deletions(-) create mode 100644 R/expect-all.R create mode 100644 tests/testthat/_snaps/expect-all.md create mode 100644 tests/testthat/test-expect-all.R diff --git a/R/expect-all.R b/R/expect-all.R new file mode 100644 index 000000000..7b0aa5f9d --- /dev/null +++ b/R/expect-all.R @@ -0,0 +1,20 @@ +expect_all_equal <- function(object, expected) { + act <- quasi_label(enquo(object)) + exp <- quasi_label(enquo(expected)) + + check_vector(act$val, error_arg = "object") + if (length(act$val) == 0) { + cli::cli_abort("{.arg object} must not be empty.") + } + + check_vector(exp$val, error_arg = "expected") + if (length(exp$val) != 1) { + cli::cli_abort("{.arg expected} must be length 1.") + } + + exp$val <- rep(exp$val, length(act$val)) + names(exp$val) <- names(act$val) + expect_waldo_equal_("Expected every element of %s to equal %s.", act, exp) + + invisible(act$val) +} diff --git a/R/expect-match.R b/R/expect-match.R index 2c6c96ed5..aaddfb46d 100644 --- a/R/expect-match.R +++ b/R/expect-match.R @@ -120,7 +120,12 @@ expect_match_ <- function( ok <- if (all) all(condition) else any(condition) if (!ok) { - values <- show_text(act$val, condition) + labels <- ifelse( + condition, + cli::col_green(cli::symbol$tick), + cli::col_red(cli::symbol$cross) + ) + values <- show_text(act$val, labels) if (length(act$val) == 1) { which <- "" } else { @@ -144,15 +149,15 @@ expect_match_ <- function( } # Adapted from print.ellmer_prompt -show_text <- function(x, matches = NULL, max_items = 20, max_lines = NULL) { - matches <- matches %||% rep(TRUE, length(x)) +show_text <- function(x, labels = NULL, max_items = 20, max_lines = NULL) { + labels <- labels %||% seq_along(x) max_lines <- max_lines %||% (max_items * 25) n <- length(x) n_extra <- length(x) - max_items if (n_extra > 0) { x <- x[seq_len(max_items)] - matches <- matches[seq_len(max_items)] + labels <- labels[seq_len(max_items)] } if (length(x) == 0) { @@ -161,13 +166,7 @@ show_text <- function(x, matches = NULL, max_items = 20, max_lines = NULL) { bar <- if (cli::is_utf8_output()) "\u2502" else "|" - id <- ifelse( - matches, - cli::col_green(cli::symbol$tick), - cli::col_red(cli::symbol$cross) - ) - - indent <- paste0(id, " ", bar, " ") + indent <- paste0(labels, " ", bar, " ") exdent <- paste0(" ", cli::col_grey(bar), " ") x[is.na(x)] <- cli::col_red("") diff --git a/tests/testthat/_snaps/expect-all.md b/tests/testthat/_snaps/expect-all.md new file mode 100644 index 000000000..ddf9756ed --- /dev/null +++ b/tests/testthat/_snaps/expect-all.md @@ -0,0 +1,56 @@ +# validates its inputs + + Code + expect_all_equal(mean, 1) + Condition + Error in `expect_all_equal()`: + ! `object` must be a vector, not a function. + Code + expect_all_equal(logical(), 1) + Condition + Error in `expect_all_equal()`: + ! `object` must not be empty. + Code + expect_all_equal(1:10, mean) + Condition + Error in `expect_all_equal()`: + ! `expected` must be a vector, not a function. + Code + expect_all_equal(1:10, 1:2) + Condition + Error in `expect_all_equal()`: + ! `expected` must be length 1. + +# can compare atomic vectors + + Code + expect_all_equal(x, TRUE) + Condition + Error: + ! Expected every element of `x` to equal TRUE. + Differences: + `actual[2:8]`: TRUE TRUE TRUE FALSE TRUE TRUE TRUE + `expected[2:8]`: TRUE TRUE TRUE TRUE TRUE TRUE TRUE + +# can compare lists + + Code + expect_all_equal(x, list(1)) + Condition + Error: + ! Expected every element of `x` to equal `list(1)`. + Differences: + `actual$c`: 2.0 + `expected$c`: 1.0 + +# truncates very long differences + + Code + expect_all_equal(x, FALSE) + Condition + Error: + ! Expected every element of `x` to equal FALSE. + Differences: + `actual`: TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE + `expected`: FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE + diff --git a/tests/testthat/_snaps/expect-match.md b/tests/testthat/_snaps/expect-match.md index 0a9902a68..63da21d43 100644 --- a/tests/testthat/_snaps/expect-match.md +++ b/tests/testthat/_snaps/expect-match.md @@ -149,20 +149,12 @@ Actual text: x | test -# show_text() shows success and failure - - Code - base::writeLines(show_text(c("a", "b"), c(TRUE, FALSE))) - Output - ✔ │ a - ✖ │ b - # show_text() truncates values and lines Code base::writeLines(show_text(lines, max_lines = 3)) Output - ✔ │ a + 1 │ a │ b │ ... ... and 8 more. @@ -170,13 +162,13 @@ Code base::writeLines(show_text(lines, max_items = 3)) Output - ✔ │ a + 1 │ a │ b │ c - ✔ │ d + 2 │ d │ e │ f - ✔ │ g + 3 │ g │ h │ i ... and 6 more. @@ -184,10 +176,10 @@ Code base::writeLines(show_text(lines, max_items = 2, max_lines = 4)) Output - ✔ │ a + 1 │ a │ b │ c - ✔ │ d + 2 │ d │ ... ... and 8 more. diff --git a/tests/testthat/test-expect-all.R b/tests/testthat/test-expect-all.R new file mode 100644 index 000000000..827ccd4db --- /dev/null +++ b/tests/testthat/test-expect-all.R @@ -0,0 +1,26 @@ +test_that("validates its inputs", { + expect_snapshot(error = TRUE, { + expect_all_equal(mean, 1) + expect_all_equal(logical(), 1) + expect_all_equal(1:10, mean) + expect_all_equal(1:10, 1:2) + }) +}) + +test_that("can compare atomic vectors", { + x <- rep(TRUE, 10) + expect_success(expect_all_equal(x, TRUE)) + + x[5] <- FALSE + expect_snapshot_failure(expect_all_equal(x, TRUE)) +}) + +test_that("can compare lists", { + x <- list(a = 1, b = 1, c = 2, d = 1, e = 1) + expect_snapshot_failure(expect_all_equal(x, list(1))) +}) + +test_that("truncates very long differences", { + x <- rep(TRUE, 10) + expect_snapshot_failure(expect_all_equal(x, FALSE)) +}) diff --git a/tests/testthat/test-expect-match.R b/tests/testthat/test-expect-match.R index 633b40435..8cc9d3b1c 100644 --- a/tests/testthat/test-expect-match.R +++ b/tests/testthat/test-expect-match.R @@ -69,13 +69,6 @@ test_that("empty string is never a match", { # show_text() ------------------------------------------------------------------ -test_that("show_text() shows success and failure", { - local_reproducible_output(unicode = TRUE) - expect_snapshot({ - base::writeLines(show_text(c("a", "b"), c(TRUE, FALSE))) - }) -}) - test_that("show_text() truncates values and lines", { local_reproducible_output(unicode = TRUE) lines <- map_chr( From cef8ccb3423b99fc7bf51da4104d8c6183de67e9 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Tue, 7 Oct 2025 15:33:58 -0500 Subject: [PATCH 02/11] Twaek test title --- tests/testthat/_snaps/expect-all.md | 2 +- tests/testthat/test-expect-all.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/_snaps/expect-all.md b/tests/testthat/_snaps/expect-all.md index ddf9756ed..23d2f8661 100644 --- a/tests/testthat/_snaps/expect-all.md +++ b/tests/testthat/_snaps/expect-all.md @@ -32,7 +32,7 @@ `actual[2:8]`: TRUE TRUE TRUE FALSE TRUE TRUE TRUE `expected[2:8]`: TRUE TRUE TRUE TRUE TRUE TRUE TRUE -# can compare lists +# can compare named lists Code expect_all_equal(x, list(1)) diff --git a/tests/testthat/test-expect-all.R b/tests/testthat/test-expect-all.R index 827ccd4db..1b026be16 100644 --- a/tests/testthat/test-expect-all.R +++ b/tests/testthat/test-expect-all.R @@ -15,7 +15,7 @@ test_that("can compare atomic vectors", { expect_snapshot_failure(expect_all_equal(x, TRUE)) }) -test_that("can compare lists", { +test_that("can compare named lists", { x <- list(a = 1, b = 1, c = 2, d = 1, e = 1) expect_snapshot_failure(expect_all_equal(x, list(1))) }) From 151621218c5ef308749086bbfe4ca72574cd931f Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Tue, 7 Oct 2025 16:12:00 -0500 Subject: [PATCH 03/11] Revert `show_text()` changes --- R/expect-match.R | 21 +++++++++++---------- tests/testthat/_snaps/expect-match.md | 12 ++++++------ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/R/expect-match.R b/R/expect-match.R index aaddfb46d..2c6c96ed5 100644 --- a/R/expect-match.R +++ b/R/expect-match.R @@ -120,12 +120,7 @@ expect_match_ <- function( ok <- if (all) all(condition) else any(condition) if (!ok) { - labels <- ifelse( - condition, - cli::col_green(cli::symbol$tick), - cli::col_red(cli::symbol$cross) - ) - values <- show_text(act$val, labels) + values <- show_text(act$val, condition) if (length(act$val) == 1) { which <- "" } else { @@ -149,15 +144,15 @@ expect_match_ <- function( } # Adapted from print.ellmer_prompt -show_text <- function(x, labels = NULL, max_items = 20, max_lines = NULL) { - labels <- labels %||% seq_along(x) +show_text <- function(x, matches = NULL, max_items = 20, max_lines = NULL) { + matches <- matches %||% rep(TRUE, length(x)) max_lines <- max_lines %||% (max_items * 25) n <- length(x) n_extra <- length(x) - max_items if (n_extra > 0) { x <- x[seq_len(max_items)] - labels <- labels[seq_len(max_items)] + matches <- matches[seq_len(max_items)] } if (length(x) == 0) { @@ -166,7 +161,13 @@ show_text <- function(x, labels = NULL, max_items = 20, max_lines = NULL) { bar <- if (cli::is_utf8_output()) "\u2502" else "|" - indent <- paste0(labels, " ", bar, " ") + id <- ifelse( + matches, + cli::col_green(cli::symbol$tick), + cli::col_red(cli::symbol$cross) + ) + + indent <- paste0(id, " ", bar, " ") exdent <- paste0(" ", cli::col_grey(bar), " ") x[is.na(x)] <- cli::col_red("") diff --git a/tests/testthat/_snaps/expect-match.md b/tests/testthat/_snaps/expect-match.md index 63da21d43..c72ab5b44 100644 --- a/tests/testthat/_snaps/expect-match.md +++ b/tests/testthat/_snaps/expect-match.md @@ -154,7 +154,7 @@ Code base::writeLines(show_text(lines, max_lines = 3)) Output - 1 │ a + ✔ │ a │ b │ ... ... and 8 more. @@ -162,13 +162,13 @@ Code base::writeLines(show_text(lines, max_items = 3)) Output - 1 │ a + ✔ │ a │ b │ c - 2 │ d + ✔ │ d │ e │ f - 3 │ g + ✔ │ g │ h │ i ... and 6 more. @@ -176,10 +176,10 @@ Code base::writeLines(show_text(lines, max_items = 2, max_lines = 4)) Output - 1 │ a + ✔ │ a │ b │ c - 2 │ d + ✔ │ d │ ... ... and 8 more. From 0feafcb95428791aa4733654e985d260624023c4 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Tue, 7 Oct 2025 16:12:37 -0500 Subject: [PATCH 04/11] And test --- tests/testthat/_snaps/expect-match.md | 8 ++++++++ tests/testthat/test-expect-match.R | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/tests/testthat/_snaps/expect-match.md b/tests/testthat/_snaps/expect-match.md index c72ab5b44..0a9902a68 100644 --- a/tests/testthat/_snaps/expect-match.md +++ b/tests/testthat/_snaps/expect-match.md @@ -149,6 +149,14 @@ Actual text: x | test +# show_text() shows success and failure + + Code + base::writeLines(show_text(c("a", "b"), c(TRUE, FALSE))) + Output + ✔ │ a + ✖ │ b + # show_text() truncates values and lines Code diff --git a/tests/testthat/test-expect-match.R b/tests/testthat/test-expect-match.R index 8cc9d3b1c..633b40435 100644 --- a/tests/testthat/test-expect-match.R +++ b/tests/testthat/test-expect-match.R @@ -69,6 +69,13 @@ test_that("empty string is never a match", { # show_text() ------------------------------------------------------------------ +test_that("show_text() shows success and failure", { + local_reproducible_output(unicode = TRUE) + expect_snapshot({ + base::writeLines(show_text(c("a", "b"), c(TRUE, FALSE))) + }) +}) + test_that("show_text() truncates values and lines", { local_reproducible_output(unicode = TRUE) lines <- map_chr( From b1fdcd64a47ffc75bf3ba72e720e0496ae25ab90 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Tue, 7 Oct 2025 17:04:50 -0500 Subject: [PATCH 05/11] Implement `expect_all_true()` and `expect_all_false()` --- R/expect-all.R | 39 ++++++++++++++++++++++++++------ tests/testthat/test-expect-all.R | 7 ++++++ 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/R/expect-all.R b/R/expect-all.R index 7b0aa5f9d..a554ba26b 100644 --- a/R/expect-all.R +++ b/R/expect-all.R @@ -2,19 +2,44 @@ expect_all_equal <- function(object, expected) { act <- quasi_label(enquo(object)) exp <- quasi_label(enquo(expected)) - check_vector(act$val, error_arg = "object") + expect_all_equal_(act, exp) + invisible(act$val) +} + +expect_all_true <- function(object) { + act <- quasi_label(enquo(object)) + exp <- labelled_value(TRUE, "TRUE") + + expect_all_equal_(act, exp) + invisible(act$val) +} + +expect_all_false <- function(object) { + act <- quasi_label(enquo(object)) + exp <- labelled_value(FALSE, "FALSE") + + expect_all_equal_(act, exp) + invisible(act$val) +} + + +expect_all_equal_ <- function(act, exp, trace_env = caller_env()) { + check_vector(act$val, error_call = trace_env, error_arg = "object") if (length(act$val) == 0) { - cli::cli_abort("{.arg object} must not be empty.") + cli::cli_abort("{.arg object} must not be empty.", call = trace_env) } - check_vector(exp$val, error_arg = "expected") + check_vector(exp$val, error_call = trace_env, error_arg = "expected") if (length(exp$val) != 1) { - cli::cli_abort("{.arg expected} must be length 1.") + cli::cli_abort("{.arg expected} must be length 1.", call = trace_env) } exp$val <- rep(exp$val, length(act$val)) names(exp$val) <- names(act$val) - expect_waldo_equal_("Expected every element of %s to equal %s.", act, exp) - - invisible(act$val) + expect_waldo_equal_( + "Expected every element of %s to equal %s.", + act, + exp, + trace_env = trace_env + ) } diff --git a/tests/testthat/test-expect-all.R b/tests/testthat/test-expect-all.R index 1b026be16..db5ccc3c4 100644 --- a/tests/testthat/test-expect-all.R +++ b/tests/testthat/test-expect-all.R @@ -24,3 +24,10 @@ test_that("truncates very long differences", { x <- rep(TRUE, 10) expect_snapshot_failure(expect_all_equal(x, FALSE)) }) + +test_that("has TRUE and FALSE helpers", { + x1 <- rep(TRUE, 10) + x2 <- rep(FALSE, 10) + expect_success(expect_all_true(x1)) + expect_success(expect_all_false(x2)) +}) From b9301317317a70489ace1b13303d7d7e454702a2 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Tue, 7 Oct 2025 17:11:49 -0500 Subject: [PATCH 06/11] Some basic docs --- NAMESPACE | 3 +++ R/expect-all.R | 22 ++++++++++++++++++++++ man/expect_all_equal.Rd | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 man/expect_all_equal.Rd diff --git a/NAMESPACE b/NAMESPACE index 426dbf1b4..63d1572e5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -79,6 +79,9 @@ export(equals_reference) export(evaluate_promise) export(exp_signal) export(expect) +export(expect_all_equal) +export(expect_all_false) +export(expect_all_true) export(expect_condition) export(expect_contains) export(expect_cpp_tests_pass) diff --git a/R/expect-all.R b/R/expect-all.R index a554ba26b..e5de35719 100644 --- a/R/expect-all.R +++ b/R/expect-all.R @@ -1,3 +1,21 @@ +#' Expect that all values in a vector are the same +#' +#' These expectations are similar to `expect_true(all(x == "x"))`, +#' `expect_true(all(x))` and `expect_true(all(!x))` but give more informative +#' failure messages if the expectations are not met. +#' +#' @inheritParams expect_equal +#' @export +#' @examples +#' x1 <- c(1, 1, 1, 1, 1, 1) +#' expect_all_equal(x1, 1) +#' +#' x2 <- c(1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2) +#' show_failure(expect_all_equal(x2, 1)) +#' +#' # expect_all_true() and expect_all_false() are helpers for common cases +#' show_failure(expect_all_true(rpois(100, 10) < 20)) +#' show_failure(expect_all_false(rpois(100, 10) > 20)) expect_all_equal <- function(object, expected) { act <- quasi_label(enquo(object)) exp <- quasi_label(enquo(expected)) @@ -6,6 +24,8 @@ expect_all_equal <- function(object, expected) { invisible(act$val) } +#' @export +#' @rdname expect_all_equal expect_all_true <- function(object) { act <- quasi_label(enquo(object)) exp <- labelled_value(TRUE, "TRUE") @@ -14,6 +34,8 @@ expect_all_true <- function(object) { invisible(act$val) } +#' @export +#' @rdname expect_all_equal expect_all_false <- function(object) { act <- quasi_label(enquo(object)) exp <- labelled_value(FALSE, "FALSE") diff --git a/man/expect_all_equal.Rd b/man/expect_all_equal.Rd new file mode 100644 index 000000000..fa95903a8 --- /dev/null +++ b/man/expect_all_equal.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/expect-all.R +\name{expect_all_equal} +\alias{expect_all_equal} +\alias{expect_all_true} +\alias{expect_all_false} +\title{Expect that all values in a vector are the same} +\usage{ +expect_all_equal(object, expected) + +expect_all_true(object) + +expect_all_false(object) +} +\arguments{ +\item{object, expected}{Computation and value to compare it to. + +Both arguments supports limited unquoting to make it easier to generate +readable failures within a function or for loop. See \link{quasi_label} for +more details.} +} +\description{ +These expectations are similar to \code{expect_true(all(x == "x"))}, +\code{expect_true(all(x))} and \code{expect_true(all(!x))} but give more informative +failure messages if the expectations are not met. +} +\examples{ +x1 <- c(1, 1, 1, 1, 1, 1) +expect_all_equal(x1, 1) + +x2 <- c(1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2) +show_failure(expect_all_equal(x2, 1)) + +# expect_all_true() and expect_all_false() are helpers for common cases +show_failure(expect_all_true(rpois(100, 10) < 20)) +show_failure(expect_all_false(rpois(100, 10) > 20)) +} From 2aed10c6f54429868529f88862db8437125d13c2 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Tue, 7 Oct 2025 17:26:04 -0500 Subject: [PATCH 07/11] Add to refence index; make title consistent --- R/expect-all.R | 2 +- _pkgdown.yml | 1 + man/expect_all_equal.Rd | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/R/expect-all.R b/R/expect-all.R index e5de35719..2ad236017 100644 --- a/R/expect-all.R +++ b/R/expect-all.R @@ -1,4 +1,4 @@ -#' Expect that all values in a vector are the same +#' Do you expect every value in a vector to have this value? #' #' These expectations are similar to `expect_true(all(x == "x"))`, #' `expect_true(all(x))` and `expect_true(all(!x))` but give more informative diff --git a/_pkgdown.yml b/_pkgdown.yml index bb17b1520..0b9aa2f12 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -27,6 +27,7 @@ reference: - title: Expectations - subtitle: Values contents: + - expect_all_equal - expect_gt - expect_length - expect_match diff --git a/man/expect_all_equal.Rd b/man/expect_all_equal.Rd index fa95903a8..8cbffea67 100644 --- a/man/expect_all_equal.Rd +++ b/man/expect_all_equal.Rd @@ -4,7 +4,7 @@ \alias{expect_all_equal} \alias{expect_all_true} \alias{expect_all_false} -\title{Expect that all values in a vector are the same} +\title{Do you expect every value in vector to have this value?} \usage{ expect_all_equal(object, expected) From 8b6bf168413061b7dbc34d52e8c61a9803752cf9 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Tue, 7 Oct 2025 17:42:21 -0500 Subject: [PATCH 08/11] Use in `expect_true()` --- R/expect-constant.R | 20 ++++++++++---------- man/expect_all_equal.Rd | 2 +- man/logical-expectations.Rd | 20 ++++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/R/expect-constant.R b/R/expect-constant.R index 2640f2c88..22267caae 100644 --- a/R/expect-constant.R +++ b/R/expect-constant.R @@ -12,17 +12,17 @@ #' @examples #' expect_true(2 == 2) #' # Failed expectations will throw an error -#' \dontrun{ -#' expect_true(2 != 2) -#' } -#' expect_true(!(2 != 2)) -#' # or better: -#' expect_false(2 != 2) +#' show_failure(expect_true(2 != 2)) #' -#' a <- 1:3 -#' expect_true(length(a) == 3) -#' # but better to use more specific expectation, if available -#' expect_equal(length(a), 3) +#' # where possible, use more specific expectations, to get more informative +#' # error messages +#' a <- 1:4 +#' show_failure(expect_true(length(a) == 3)) +#' show_failure(expect_equal(length(a), 3)) +#' +#' x <- c(TRUE, TRUE, FALSE, TRUE) +#' show_failure(expect_true(all(x))) +#' show_failure(expect_all_true(x)) #' @name logical-expectations NULL diff --git a/man/expect_all_equal.Rd b/man/expect_all_equal.Rd index 8cbffea67..dfe378df3 100644 --- a/man/expect_all_equal.Rd +++ b/man/expect_all_equal.Rd @@ -4,7 +4,7 @@ \alias{expect_all_equal} \alias{expect_all_true} \alias{expect_all_false} -\title{Do you expect every value in vector to have this value?} +\title{Do you expect every value in a vector to have this value?} \usage{ expect_all_equal(object, expected) diff --git a/man/logical-expectations.Rd b/man/logical-expectations.Rd index ff6c90539..56f4e961c 100644 --- a/man/logical-expectations.Rd +++ b/man/logical-expectations.Rd @@ -32,17 +32,17 @@ Attributes are ignored. \examples{ expect_true(2 == 2) # Failed expectations will throw an error -\dontrun{ -expect_true(2 != 2) -} -expect_true(!(2 != 2)) -# or better: -expect_false(2 != 2) +show_failure(expect_true(2 != 2)) + +# where possible, use more specific expectations, to get more informative +# error messages +a <- 1:4 +show_failure(expect_true(length(a) == 3)) +show_failure(expect_equal(length(a), 3)) -a <- 1:3 -expect_true(length(a) == 3) -# but better to use more specific expectation, if available -expect_equal(length(a), 3) +x <- c(TRUE, TRUE, FALSE, TRUE) +show_failure(expect_true(all(x))) +show_failure(expect_all_true(x)) } \seealso{ Other expectations: From 565eb14823c4d2113035d45492407a50f596006e Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Wed, 8 Oct 2025 07:48:12 -0500 Subject: [PATCH 09/11] Enable tolerance --- R/expect-all.R | 1 + tests/testthat/test-expect-all.R | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/R/expect-all.R b/R/expect-all.R index 2ad236017..cc66c4ce7 100644 --- a/R/expect-all.R +++ b/R/expect-all.R @@ -62,6 +62,7 @@ expect_all_equal_ <- function(act, exp, trace_env = caller_env()) { "Expected every element of %s to equal %s.", act, exp, + tolerance = testthat_tolerance(), trace_env = trace_env ) } diff --git a/tests/testthat/test-expect-all.R b/tests/testthat/test-expect-all.R index db5ccc3c4..f2a525f50 100644 --- a/tests/testthat/test-expect-all.R +++ b/tests/testthat/test-expect-all.R @@ -20,6 +20,10 @@ test_that("can compare named lists", { expect_snapshot_failure(expect_all_equal(x, list(1))) }) +test_that("has tolerance enabled", { + expect_success(expect_all_equal(1, 1L)) +}) + test_that("truncates very long differences", { x <- rep(TRUE, 10) expect_snapshot_failure(expect_all_equal(x, FALSE)) From c5719e6195bf50baad5a95d3166b40351cd71c6d Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Wed, 8 Oct 2025 07:49:00 -0500 Subject: [PATCH 10/11] Set a seed --- R/expect-all.R | 1 + man/expect_all_equal.Rd | 1 + 2 files changed, 2 insertions(+) diff --git a/R/expect-all.R b/R/expect-all.R index cc66c4ce7..cce8ec32c 100644 --- a/R/expect-all.R +++ b/R/expect-all.R @@ -14,6 +14,7 @@ #' show_failure(expect_all_equal(x2, 1)) #' #' # expect_all_true() and expect_all_false() are helpers for common cases +#' set.seed(1016) #' show_failure(expect_all_true(rpois(100, 10) < 20)) #' show_failure(expect_all_false(rpois(100, 10) > 20)) expect_all_equal <- function(object, expected) { diff --git a/man/expect_all_equal.Rd b/man/expect_all_equal.Rd index dfe378df3..b230b3b3f 100644 --- a/man/expect_all_equal.Rd +++ b/man/expect_all_equal.Rd @@ -32,6 +32,7 @@ x2 <- c(1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2) show_failure(expect_all_equal(x2, 1)) # expect_all_true() and expect_all_false() are helpers for common cases +set.seed(1016) show_failure(expect_all_true(rpois(100, 10) < 20)) show_failure(expect_all_false(rpois(100, 10) > 20)) } From 4e2e0d404c1b91382d381f9656b9e7e833e7584a Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Wed, 8 Oct 2025 07:51:14 -0500 Subject: [PATCH 11/11] Add news bullet --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 207716bf1..3137332a6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # testthat (development version) +* `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). * 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). * `set_state_inspector()` gains `tolerance` argument and ignores minor FP differences by default (@mcol, #2237). * `expect_vector()` fails, instead of erroring, if `object` is not a vector (@plietar, #2224).