Skip to content

Commit 0986b18

Browse files
authored
make expect_lt() etc. work properly for non-numeric data (#2269)
* make `expect_lt()` etc. work properly for non-numeric data And ensure it works with negative values. Fixes #2268
1 parent 10b328b commit 0986b18

File tree

5 files changed

+137
-30
lines changed

5 files changed

+137
-30
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# testthat (development version)
22

3+
* Make `expect_lt()`, `expect_lte()`, `expect_gt()`, and `expect_gte()` work properly for non-numeric data (#2268)
34
* New `expect_disjoint()` to check for the absence of values (@stibu81, #1851).
45
* `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).
56
* 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).

R/expect-comparison.R

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
#' Do you expect a number bigger or smaller than this?
1+
#' Do you expect a value bigger or smaller than this?
2+
#'
3+
#' These functions compare values of comparable data types, such as numbers,
4+
#' dates, and times.
25
#'
36
#' @inheritParams expect_equal
47
#' @param object,expected A value to compare and its expected bound.
@@ -45,30 +48,56 @@ expect_compare_ <- function(
4548
failure_compare <- function(act, exp, operator) {
4649
actual_op <- switch(operator, "<" = ">=", "<=" = ">", ">" = "<=", ">=" = "<")
4750

48-
diff <- act$val - exp$val
4951
msg_exp <- sprintf("Expected %s %s %s.", act$lab, operator, exp$lab)
5052

51-
digits <- max(
52-
digits(act$val),
53-
digits(exp$val),
54-
min_digits(act$val, exp$val)
55-
)
56-
57-
msg_act <- sprintf(
58-
"Actual comparison: %s %s %s",
59-
num_exact(act$val, digits),
60-
actual_op,
61-
num_exact(exp$val, digits)
62-
)
63-
64-
if (is.na(diff)) {
65-
msg_diff <- NULL
53+
if (is.numeric(act$val)) {
54+
digits <- max(
55+
digits(act$val),
56+
digits(exp$val),
57+
min_digits(act$val, exp$val)
58+
)
59+
60+
msg_act <- sprintf(
61+
"Actual comparison: %s %s %s",
62+
num_exact(act$val, digits),
63+
actual_op,
64+
num_exact(exp$val, digits)
65+
)
66+
67+
diff <- act$val - exp$val
68+
if (is.na(diff)) {
69+
msg_diff <- NULL
70+
} else {
71+
msg_diff <- sprintf(
72+
"Difference: %s %s 0",
73+
num_exact(diff, digits),
74+
actual_op
75+
)
76+
}
77+
6678
} else {
67-
msg_diff <- sprintf(
68-
"Difference: %s %s 0",
69-
num_exact(diff, digits),
70-
actual_op
79+
msg_act <- sprintf(
80+
"Actual comparison: \"%s\" %s \"%s\"",
81+
act$val,
82+
actual_op,
83+
exp$val
7184
)
85+
86+
if (inherits(act$val, c("Date", "POSIXt"))) {
87+
diff <- act$val - exp$val
88+
if (is.na(diff)) {
89+
msg_diff <- NULL
90+
} else {
91+
msg_diff <- sprintf(
92+
"Difference: %s %s 0 %s",
93+
dt_diff(diff),
94+
actual_op,
95+
attr(diff, "unit")
96+
)
97+
}
98+
} else {
99+
msg_diff <- NULL
100+
}
72101
}
73102

74103
c(msg_exp, msg_act, msg_diff)
@@ -165,10 +194,16 @@ digits <- function(x) {
165194
if (length(x) == 0) {
166195
return(0)
167196
}
168-
scale <- -log10(min(x))
197+
scale <- -log10(min(abs(x)))
169198
if (scale <= 0) {
170199
0L
171200
} else {
172201
ceiling(round(scale, digits = 2))
173202
}
174203
}
204+
205+
dt_diff <- function(x) {
206+
val <- unclass(x)
207+
digits <- digits(abs(val)) + 1
208+
paste(num_exact(val, digits), attr(x, "unit"))
209+
}

man/comparison-expectations.Rd

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

tests/testthat/_snaps/expect-comparison.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,45 @@
7676
! Expected `x` < 10.
7777
Actual comparison: NA >= 10.0
7878

79+
# comparisons with negative numbers work
80+
81+
Code
82+
expect_gt(-5, -2)
83+
Condition
84+
Error:
85+
! Expected `-5` > `-2`.
86+
Actual comparison: -5.0 <= -2.0
87+
Difference: -3.0 <= 0
88+
89+
# comparisons with POSIXct objects work
90+
91+
Code
92+
expect_lt(time2, time)
93+
Condition
94+
Error:
95+
! Expected `time2` < `time`.
96+
Actual comparison: "2020-01-01 01:00:01.5" >= "2020-01-01 01:00:00"
97+
Difference: 1.5 secs >= 0 secs
98+
99+
# comparisons with Date objects work
100+
101+
Code
102+
expect_gt(date, date2)
103+
Condition
104+
Error:
105+
! Expected `date` > `date2`.
106+
Actual comparison: "2020-01-01" <= "2020-01-02"
107+
Difference: -1.0 days <= 0 days
108+
109+
# comparisons with character objects work
110+
111+
Code
112+
expect_lte("b", "a")
113+
Condition
114+
Error:
115+
! Expected "b" <= "a".
116+
Actual comparison: "b" > "a"
117+
79118
# comparison must yield a single logical
80119

81120
Code

tests/testthat/test-expect-comparison.R

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,44 @@ test_that("comparisons with NA work", {
5858
expect_snapshot_failure(expect_lt(x, 10))
5959
})
6060

61-
test_that("comparisons with more complicated objects work", {
62-
time <- Sys.time()
63-
time2 <- time + 1
61+
62+
test_that("comparisons with negative numbers work", {
63+
expect_success(expect_lt(-5, -2))
64+
expect_snapshot_failure(expect_gt(-5, -2))
65+
})
66+
67+
test_that("comparisons with POSIXct objects work", {
68+
time <- as.POSIXct("2020-01-01 01:00:00")
69+
time2 <- time + 1.5
6470
expect_success(expect_lt(time, time2))
65-
expect_success(expect_lte(time, time2))
66-
expect_success(expect_gt(time2, time))
67-
expect_success(expect_gte(time2, time))
71+
72+
# set digits.secs = 1 to ensure consistent output with older R versions
73+
withr::with_options(c(digits.secs = 1), {
74+
expect_snapshot_failure(expect_lt(time2, time))
75+
})
76+
})
77+
78+
test_that("comparisons with Date objects work", {
79+
date <- as.Date("2020-01-01")
80+
date2 <- date + 1
81+
expect_success(expect_gt(date2, date))
82+
expect_success(expect_gte(date2, date))
83+
84+
expect_snapshot_failure(expect_gt(date, date2))
85+
})
86+
87+
test_that("comparisons of date/time with NA work", {
88+
time <- as.POSIXct("2020-01-01 01:00:00")
89+
date <- as.Date("2020-01-01")
90+
91+
expect_failure(expect_lt(time, NA))
92+
expect_failure(expect_gt(date, NA))
93+
})
94+
95+
test_that("comparisons with character objects work", {
96+
expect_success(expect_lte("a", "b"))
97+
98+
expect_snapshot_failure(expect_lte("b", "a"))
6899
})
69100

70101
test_that("comparison must yield a single logical", {

0 commit comments

Comments
 (0)