Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# testthat (development version)

* `expect_lt()`, `expect_gt()`, and friends have a refined display that is more likely to display the correct number of digits and shows you the actual values compared.
* `ParallelProgressReporter` now respect `max_failures` (#1162).
* 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.
* New `expect_r6_class()` (#2030).
Expand Down
50 changes: 46 additions & 4 deletions R/expect-comparison.R
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,27 @@ expect_compare_ <- function(
msg <- c(
"<" = "not strictly less than",
"<=" = "not less than",
">" = "not strictly more than",
">=" = "not more than"
">" = "not strictly greater than",
">=" = "not greater than"
)[[operator]]

negated_op <- switch(operator, "<" = ">", "<=" = ">=", ">" = "<", ">=" = "<=")
Copy link
Contributor

@MichaelChirico MichaelChirico Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't these wrong? e.g. I think we'll get

expect_snapshot_failure(expect_gt(x, 1.0 * x))
    `x` is not strictly greater than 1.0 * x.
    0.0000100 - 0.0000110 = 0 < 0

The negated open interval is a closed interval, and vice versa.

https://github.com/r-lib/lintr/blob/84d3f8c5f4b3576defe880872ec413df8986ef9c/R/comparison_negation_linter.R#L40

(even if I'm reading it wrong we'll still want a test of the edge case)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooops, yes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also that revealed a bug when the number of digits in the difference is much smaller than the values.


cmp <- op(act$val, exp$val)
if (length(cmp) != 1 || !is.logical(cmp)) {
abort("Result of comparison must be a single logical value")
}
if (!isTRUE(cmp)) {
digits <- min_digits(act$val, exp$val)
msg <- sprintf(
"%s is %s %s. Difference: %.3g",
"%s is %s %s.\n%s - %s = %s %s 0",
act$lab,
msg,
exp$lab,
act$val - exp$val
num_exact(act$val, digits),
num_exact(exp$val, digits),
num_exact(act$val - exp$val, digits),
negated_op
)
return(fail(msg, trace_env = trace_env))
}
Expand Down Expand Up @@ -109,3 +115,39 @@ expect_more_than <- function(...) {
warning("Deprecated: please use `expect_gt()` instead", call. = FALSE)
expect_gt(...)
}


# Helpers -----------------------------------------------------------------

num_exact <- function(x, digits = 6) {
sprintf(paste0("%0.", digits, "f"), x)
}

min_digits <- function(x, y, tolerance = testthat_tolerance()) {
if (is.integer(x) && is.integer(y)) {
return(0L)
}

attributes(x) <- NULL
attributes(y) <- NULL

n <- digits(abs(x - y))
if (!is.null(tolerance)) {
n <- min(n, digits(tolerance))
}

as.integer(n) + 1L
}

digits <- function(x) {
x <- x[!is.na(x) & x != 0]
if (length(x) == 0) {
return(0)
}
scale <- -log10(min(x))
if (scale <= 0) {
0L
} else {
ceiling(round(scale, digits = 2))
}
}
10 changes: 10 additions & 0 deletions tests/testthat/_snaps/expect-comparison.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# useful output when numbers are very small

1.1 * x is not less than `x`.
0.0000110 - 0.0000100 = 0.0000010 >= 0

---

`x` is not strictly greater than 1.1 * x.
0.0000100 - 0.0000110 = -0.0000010 < 0

6 changes: 6 additions & 0 deletions tests/testthat/test-expect-comparison.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ test_that("basic comparisons work", {
expect_success(expect_gte(10, 10))
})

test_that("useful output when numbers are very small", {
x <- 1e-5
expect_snapshot_failure(expect_lte(1.1 * x, x))
expect_snapshot_failure(expect_gt(x, 1.1 * x))
})

test_that("comparison result object invisibly", {
out <- expect_invisible(expect_lt(1, 10))
expect_equal(out, 1)
Expand Down
Loading