Skip to content

Commit 2896db2

Browse files
authored
Overhaul expect_matches() (#2138)
Use `expect_snapshot_failure()` to look at more of the messaging, and then use that to make a bunch of minor improvements. Also polish docs and improve argument checking.
1 parent 9bfe131 commit 2896db2

File tree

8 files changed

+115
-92
lines changed

8 files changed

+115
-92
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+
* `expect_matches()` failures should be a little easier to read (#2135).
34
* New `local_on_cran(TRUE)` allows you to simulate how your tests will run on CRAN (#2112).
45
* `expect_no_*()` now executes the entire code block, rather than stopping at the first message or warning (#1991).
56
* `expect_no_failures()` and `expect_no_successes()` are now deprecated as `expect_success()` now test for no failures and `expect_failure()` tests for no successes (#)

R/expect-match.R

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#' Does a string match a regular expression?
22
#'
33
#' @details
4-
#' `expect_match()` is a wrapper around [grepl()]. See its documentation for
5-
#' more detail about the individual arguments. `expect_no_match()` provides
6-
#' the complementary case, checking that a string *does not* match a regular
7-
#' expression.
4+
#' `expect_match()` checks if a character vector matches a regular expression,
5+
#' powered by [grepl()].
6+
#'
7+
#' `expect_no_match()` provides the complementary case, checking that a
8+
#' character vector *does not* match a regular expression.
89
#'
910
#' @inheritParams expect_that
1011
#' @inheritParams base::grepl
@@ -21,12 +22,11 @@
2122
#' expect_match("Testing is fun", "f.n")
2223
#' expect_no_match("Testing is fun", "horrible")
2324
#'
24-
#' \dontrun{
25-
#' expect_match("Testing is fun", "horrible")
25+
#' show_failure(expect_match("Testing is fun", "horrible"))
26+
#' show_failure(expect_match("Testing is fun", "horrible", fixed = TRUE))
2627
#'
2728
#' # Zero-length inputs always fail
28-
#' expect_match(character(), ".")
29-
#' }
29+
#' show_failure(expect_match(character(), "."))
3030
expect_match <- function(
3131
object,
3232
regexp,
@@ -37,11 +37,14 @@ expect_match <- function(
3737
info = NULL,
3838
label = NULL
3939
) {
40-
# Capture here to avoid environment-related messiness
4140
act <- quasi_label(enquo(object), label, arg = "object")
42-
stopifnot(is.character(regexp), length(regexp) == 1)
4341

44-
stopifnot(is.character(act$val))
42+
check_character(object)
43+
check_string(regexp)
44+
check_bool(perl)
45+
check_bool(fixed)
46+
check_bool(all)
47+
4548
if (length(object) == 0) {
4649
return(fail(sprintf("%s is empty.", act$lab), info = info))
4750
}
@@ -75,11 +78,7 @@ expect_no_match <- function(
7578
# Capture here to avoid environment-related messiness
7679
act <- quasi_label(enquo(object), label, arg = "object")
7780
stopifnot(is.character(regexp), length(regexp) == 1)
78-
7981
stopifnot(is.character(act$val))
80-
if (length(object) == 0) {
81-
return(fail(sprintf("%s is empty.", act$lab), info = info))
82-
}
8382

8483
expect_match_(
8584
act = act,
@@ -114,20 +113,17 @@ expect_match_ <- function(
114113
return(pass(act$val))
115114
}
116115

117-
escape <- if (fixed) identity else escape_regex
118-
116+
text <- encodeString(act$val)
119117
if (length(act$val) == 1) {
120-
values <- paste0("Actual value: \"", escape(encodeString(act$val)), "\"")
118+
values <- paste0('Text: "', text, '"')
121119
} else {
122-
values <- paste0(
123-
"Actual values:\n",
124-
paste0("* ", escape(encodeString(act$val)), collapse = "\n")
125-
)
120+
values <- paste0("Text:\n", paste0("* ", text, collapse = "\n"))
126121
}
127122

128123
msg <- sprintf(
129-
if (negate) "%s does match %s.\n%s" else "%s does not match %s.\n%s",
130-
escape(act$lab),
124+
if (negate) "%s matches %s %s.\n%s" else "%s does not match %s %s.\n%s",
125+
act$lab,
126+
if (fixed) "string" else "regexp",
131127
encodeString(regexp, quote = '"'),
132128
values
133129
)

R/utils.R

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,6 @@
22
#' @export
33
magrittr::`%>%`
44

5-
escape_regex <- function(x) {
6-
chars <- c(
7-
"*",
8-
".",
9-
"?",
10-
"^",
11-
"+",
12-
"$",
13-
"|",
14-
"(",
15-
")",
16-
"[",
17-
"]",
18-
"{",
19-
"}",
20-
"\\"
21-
)
22-
gsub(
23-
paste0("([\\", paste0(collapse = "\\", chars), "])"),
24-
"\\\\\\1",
25-
x,
26-
perl = TRUE
27-
)
28-
}
29-
305
can_entrace <- function(cnd) {
316
!inherits(cnd, "Throwable")
327
}

man/expect_match.Rd

Lines changed: 3 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# generates useful failure messages
2+
3+
`zero` is empty.
4+
5+
---
6+
7+
`one` does not match regexp "asdf".
8+
Text: "bcde"
9+
10+
---
11+
12+
`many` does not match regexp "asdf".
13+
Text:
14+
* a
15+
* b
16+
* c
17+
* d
18+
* e
19+
20+
# checks its inputs
21+
22+
Code
23+
expect_match(1)
24+
Condition
25+
Error in `expect_match()`:
26+
! `object` must be a character vector, not the number 1.
27+
Code
28+
expect_match("x", 1)
29+
Condition
30+
Error in `expect_match()`:
31+
! `regexp` must be a single string, not the number 1.
32+
Code
33+
expect_match("x", "x", fixed = 1)
34+
Condition
35+
Error in `expect_match()`:
36+
! `fixed` must be `TRUE` or `FALSE`, not the number 1.
37+
Code
38+
expect_match("x", "x", perl = 1)
39+
Condition
40+
Error in `expect_match()`:
41+
! `perl` must be `TRUE` or `FALSE`, not the number 1.
42+
Code
43+
expect_match("x", "x", all = 1)
44+
Condition
45+
Error in `expect_match()`:
46+
! `all` must be `TRUE` or `FALSE`, not the number 1.
47+
48+
# expect_no_match works
49+
50+
`x` matches string "e*".
51+
Text: "te*st"
52+
53+
---
54+
55+
`x` matches regexp "TEST".
56+
Text: "test"
57+

tests/testthat/_snaps/expect-output.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# expect = string checks for match
22

3-
`g\(\)` does not match "x".
4-
Actual value: "!"
3+
`g()` does not match regexp "x".
4+
Text: "!"
55

66
---
77

tests/testthat/_snaps/expect-self-test.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# expect_failure() can optionally match message
22

3-
Failure message does not match "banana".
4-
Actual value: "apple"
3+
Failure message does not match regexp "banana".
4+
Text: "apple"
55

66
# errors in expect_success bubble up
77

tests/testthat/test-expect-match.R

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,42 @@
1-
test_that("extra arguments to matches passed onto grepl", {
2-
expect_success(expect_match("te*st", "e*", fixed = TRUE))
3-
expect_success(expect_match("test", "TEST", ignore.case = TRUE))
4-
})
1+
test_that("generates useful failure messages", {
2+
zero <- character(0)
3+
expect_snapshot_failure(expect_match(zero, 'asdf'))
54

6-
test_that("special regex characters are escaped in output", {
7-
error <- tryCatch(
8-
expect_match("f() test", "f() test"),
9-
expectation = function(e) e$message
10-
)
11-
expect_equal(
12-
error,
13-
"\"f\\(\\) test\" does not match \"f() test\".\nActual value: \"f\\(\\) test\""
14-
)
15-
})
5+
one <- "bcde"
6+
expect_snapshot_failure(expect_match(one, 'asdf'))
167

17-
test_that("correct reporting of expected label", {
18-
expect_failure(expect_match("[a]", "[b]"), escape_regex("[a]"), fixed = TRUE)
19-
expect_failure(expect_match("[a]", "[b]", fixed = TRUE), "[a]", fixed = TRUE)
8+
many <- letters[1:5]
9+
expect_snapshot_failure(expect_match(many, 'asdf'))
2010
})
2111

22-
test_that("errors if obj is empty str", {
23-
x <- character(0)
24-
err <- expect_error(
25-
expect_match(x, 'asdf'),
26-
class = "expectation_failure"
27-
)
28-
expect_match(err$message, 'is empty')
12+
test_that("checks its inputs", {
13+
expect_snapshot(error = TRUE, {
14+
expect_match(1)
15+
expect_match("x", 1)
16+
expect_match("x", "x", fixed = 1)
17+
expect_match("x", "x", perl = 1)
18+
expect_match("x", "x", all = 1)
19+
})
2920
})
3021

31-
test_that("prints multiple unmatched values", {
32-
err <- expect_error(
33-
expect_match(letters[1:10], 'asdf'),
34-
class = "expectation_failure"
35-
)
36-
expect_match(err$message, "does not match")
22+
test_that("extra arguments passed onto grepl", {
23+
expect_failure(expect_match("\\s", "\\s"))
24+
expect_success(expect_match("\\s", "\\s", fixed = TRUE))
25+
26+
expect_failure(expect_match("test", "TEST"))
27+
expect_success(expect_match("test", "TEST", ignore.case = TRUE))
3728
})
3829

3930
test_that("expect_no_match works", {
4031
expect_success(expect_no_match("[a]", "[b]"))
4132
expect_success(expect_no_match("[a]", "[b]", fixed = TRUE))
42-
expect_failure(
43-
expect_no_match("te*st", "e*", fixed = TRUE),
44-
escape_regex("te*st")
45-
)
46-
expect_failure(expect_no_match("test", "TEST", ignore.case = TRUE), "test")
33+
34+
x <- "te*st"
35+
expect_snapshot_failure(expect_no_match(x, "e*", fixed = TRUE))
36+
x <- "test"
37+
expect_snapshot_failure(expect_no_match(x, "TEST", ignore.case = TRUE))
38+
})
39+
40+
test_that("empty string is never a match", {
41+
expect_success(expect_no_match(character(), "x"))
4742
})

0 commit comments

Comments
 (0)