From 806c68aa5339e3ad1546a36c32c156a53af0acac Mon Sep 17 00:00:00 2001 From: Marco Colombo Date: Wed, 17 Sep 2025 23:17:27 +0200 Subject: [PATCH 1/4] Allow to set a tolerance for set_state_inspector(). --- NEWS.md | 1 + R/test-state.R | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index a9ddc93db..c20e53688 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # testthat (development version) +* `set_state_inspector()` now allows to control the tolerance (#2237). * New `vignette("mocking")` explains mocking in detail (#1265). * New `vignette("challenging-functions")` provides an index to other documentation organised by testing challenges (#1265). * When running a test interactively, testthat now reports the number of succeses. The results should also be more useful if you are using nested tests. diff --git a/R/test-state.R b/R/test-state.R index ff1320314..ce4744078 100644 --- a/R/test-state.R +++ b/R/test-state.R @@ -42,7 +42,9 @@ #' @export #' @param callback Either a zero-argument function that returns an object #' capturing global state that you're interested in, or `NULL`. -set_state_inspector <- function(callback) { +#' @param tolerance Numerical tolerance: any differences (in the sense of +#' [base::all.equal()]) smaller than this value will be ignored. +set_state_inspector <- function(callback, tolerance = testthat_tolerance()) { if ( !is.null(callback) && !(is.function(callback) && length(formals(callback)) == 0) @@ -51,11 +53,13 @@ set_state_inspector <- function(callback) { } the$state_inspector <- callback + the$state_inspector_tolerance <- tolerance invisible() } testthat_state_condition <- function(before, after, call) { - diffs <- waldo_compare(before, after, x_arg = "before", y_arg = "after") + diffs <- waldo_compare(before, after, x_arg = "before", y_arg = "after", + tolerance = the$state_inspector_tolerance) if (length(diffs) == 0) { return(NULL) From 1479fd2faaee3b00abdd3a3d6bb1ae75587bd472 Mon Sep 17 00:00:00 2001 From: Marco Colombo Date: Wed, 17 Sep 2025 23:33:47 +0200 Subject: [PATCH 2/4] Roxygenise(). --- man/set_state_inspector.Rd | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/man/set_state_inspector.Rd b/man/set_state_inspector.Rd index 08ae0740b..a8cdac405 100644 --- a/man/set_state_inspector.Rd +++ b/man/set_state_inspector.Rd @@ -4,11 +4,14 @@ \alias{set_state_inspector} \title{Check for global state changes} \usage{ -set_state_inspector(callback) +set_state_inspector(callback, tolerance = testthat_tolerance()) } \arguments{ \item{callback}{Either a zero-argument function that returns an object capturing global state that you're interested in, or \code{NULL}.} + +\item{tolerance}{Numerical tolerance: any differences (in the sense of +\code{\link[base:all.equal]{base::all.equal()}}) smaller than this value will be ignored.} } \description{ One of the most pernicious challenges to debug is when a test runs fine From ba4ec9e78046cb749ff43cf44c5a8a30ce655ccd Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Thu, 2 Oct 2025 15:06:52 -0400 Subject: [PATCH 3/4] Format --- R/test-state.R | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/R/test-state.R b/R/test-state.R index ce4744078..1ab4fc03b 100644 --- a/R/test-state.R +++ b/R/test-state.R @@ -58,8 +58,13 @@ set_state_inspector <- function(callback, tolerance = testthat_tolerance()) { } testthat_state_condition <- function(before, after, call) { - diffs <- waldo_compare(before, after, x_arg = "before", y_arg = "after", - tolerance = the$state_inspector_tolerance) + diffs <- waldo_compare( + before, + after, + x_arg = "before", + y_arg = "after", + tolerance = the$state_inspector_tolerance + ) if (length(diffs) == 0) { return(NULL) From 620035a32fcf72f0548dbe46a18d9ce64363782d Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Thu, 2 Oct 2025 15:09:37 -0400 Subject: [PATCH 4/4] Inherit tolerance docs --- DESCRIPTION | 2 +- NEWS.md | 2 +- R/test-state.R | 3 +-- man/expect_vector.Rd | 2 +- man/set_state_inspector.Rd | 14 ++++++++++++-- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 1116b12b3..2bd079626 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -57,4 +57,4 @@ Config/testthat/parallel: true Config/testthat/start-first: watcher, parallel* Encoding: UTF-8 Roxygen: list(markdown = TRUE, r6 = FALSE) -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 diff --git a/NEWS.md b/NEWS.md index c20e53688..0b48c709d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # testthat (development version) -* `set_state_inspector()` now allows to control the tolerance (#2237). +* `set_state_inspector()` gains `tolerance` argument and ignores minor FP differences by default (@mcol, #2237). * New `vignette("mocking")` explains mocking in detail (#1265). * New `vignette("challenging-functions")` provides an index to other documentation organised by testing challenges (#1265). * When running a test interactively, testthat now reports the number of succeses. The results should also be more useful if you are using nested tests. diff --git a/R/test-state.R b/R/test-state.R index 1ab4fc03b..6fb878515 100644 --- a/R/test-state.R +++ b/R/test-state.R @@ -42,8 +42,7 @@ #' @export #' @param callback Either a zero-argument function that returns an object #' capturing global state that you're interested in, or `NULL`. -#' @param tolerance Numerical tolerance: any differences (in the sense of -#' [base::all.equal()]) smaller than this value will be ignored. +#' @inheritParams waldo::compare set_state_inspector <- function(callback, tolerance = testthat_tolerance()) { if ( !is.null(callback) && diff --git a/man/expect_vector.Rd b/man/expect_vector.Rd index c67323e16..9e9b7463c 100644 --- a/man/expect_vector.Rd +++ b/man/expect_vector.Rd @@ -24,7 +24,7 @@ means that it used the vctrs of \code{ptype} (prototype) and \code{size}. See details in \url{https://vctrs.r-lib.org/articles/type-size.html} } \examples{ -\dontshow{if (requireNamespace("vctrs")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (requireNamespace("vctrs")) withAutoprint(\{ # examplesIf} expect_vector(1:10, ptype = integer(), size = 10) show_failure(expect_vector(1:10, ptype = integer(), size = 5)) show_failure(expect_vector(1:10, ptype = character(), size = 5)) diff --git a/man/set_state_inspector.Rd b/man/set_state_inspector.Rd index a8cdac405..61e52f1b1 100644 --- a/man/set_state_inspector.Rd +++ b/man/set_state_inspector.Rd @@ -10,8 +10,18 @@ set_state_inspector(callback, tolerance = testthat_tolerance()) \item{callback}{Either a zero-argument function that returns an object capturing global state that you're interested in, or \code{NULL}.} -\item{tolerance}{Numerical tolerance: any differences (in the sense of -\code{\link[base:all.equal]{base::all.equal()}}) smaller than this value will be ignored.} +\item{tolerance}{If non-\code{NULL}, used as threshold for ignoring small +floating point difference when comparing numeric vectors. Using any +non-\code{NULL} value will cause integer and double vectors to be compared +based on their values, not their types, and will ignore the difference +between \code{NaN} and \code{NA_real_}. + +It uses the same algorithm as \code{\link[=all.equal]{all.equal()}}, i.e., first we generate +\code{x_diff} and \code{y_diff} by subsetting \code{x} and \code{y} to look only locations +with differences. Then we check that +\code{mean(abs(x_diff - y_diff)) / mean(abs(y_diff))} (or just +\code{mean(abs(x_diff - y_diff))} if \code{y_diff} is small) is less than +\code{tolerance}.} } \description{ One of the most pernicious challenges to debug is when a test runs fine