Skip to content

Use conditions for reporting results #360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Feb 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7339f0d
test_that() return ok value from test_code()
Feb 5, 2016
5448986
update ok in enclosing environment
Feb 5, 2016
92aeb39
add test (currently failing for case of test failure)
Feb 5, 2016
6953645
results are handled using conditions and restarts
Feb 5, 2016
68249e8
use report_results() everywhere
Feb 5, 2016
5e105d6
tests for special and new-style expectations
Feb 5, 2016
3d6dd33
use signalCondition if tests passed
Feb 5, 2016
f1018a8
typo
Feb 5, 2016
46795a6
add test for error behavior with bare expectations
Feb 5, 2016
12e8d57
remove commented code
Feb 5, 2016
00d4d90
also test skip()
Feb 5, 2016
8481fd1
check return value in test_test_that()
Feb 5, 2016
0deb2e0
document (unrelated)
Feb 5, 2016
77d51dc
only stop if condition not handled and test didn't pass
Feb 5, 2016
e450fbc
simplify bare tests
Feb 5, 2016
58ab45c
move report_results
Feb 5, 2016
ca3b8b8
use signalCondition() to signal success, and stop() to signal failure
Feb 12, 2016
8424b89
update tests
Feb 12, 2016
84abb46
expectations are now first-class condition objects
Feb 18, 2016
485cbcb
minor refactoring
Feb 18, 2016
371f500
wording
Feb 18, 2016
d9cf5d4
Merge commit 'origin/master~10' into feature/return-value
Feb 19, 2016
ed95622
Merge commit 'origin/master~9' into feature/return-value
Feb 19, 2016
29372d8
Merge commit 'origin/master~5' into feature/return-value
Feb 19, 2016
67c91c9
Merge commit 'origin/master~4' into feature/return-value
Feb 19, 2016
20ac9ea
Merge commit 'origin/master~2' into feature/return-value
Feb 19, 2016
72ba350
Merge commit 'origin/master~1' into feature/return-value
Feb 19, 2016
c3e6137
Merge remote-tracking branch 'origin/master' into feature/return-value
Feb 19, 2016
26b3c41
Merge remote-tracking branch 'origin/master' into feature/return-value
Feb 19, 2016
2b5c28c
get rid of success_msg again
Feb 19, 2016
5b45d70
extract expectation_ok()
Feb 19, 2016
9d62f77
Merge remote-tracking branch 'origin/master' into feature/return-value
Feb 19, 2016
e6b0cd6
NEWS
Feb 19, 2016
cfd0489
move test
Feb 19, 2016
116e60d
usage of expectation_ok()
Feb 19, 2016
d1fb341
oops
Feb 19, 2016
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
5 changes: 5 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

S3method(as.character,expectation)
S3method(as.data.frame,testthat_results)
S3method(as.expectation,default)
S3method(as.expectation,error)
S3method(as.expectation,expectation)
S3method(as.expectation,logical)
S3method(as.expectation,skip)
S3method(compare,POSIXt)
S3method(compare,character)
S3method(compare,default)
Expand Down
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# testthat 0.11.0.9000

* Refactored internal testing logic. Now all expectations are also conditions, and R's condition system is used to signal failures and successes (#360, @krlmlr).

* `test_that()` returns a `logical` that indicates if all tests were successful (#360, @krlmlr).

* `find_reporter()` (and also all high-level testing functions) support a vector of reporters. For more than one reporter, a `MultiReporter` is created (#307, @krlmlr).

* `with_reporter()` is used internally and gains new argument `start_end_reporter = TRUE` (@krlmlr, 355).
Expand Down
28 changes: 9 additions & 19 deletions R/expect-that.r
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,13 @@ expect_that <- function(object, condition, info = NULL, label = NULL) {
stopifnot(length(info) <= 1, length(label) <= 1)

label <- label %||% find_expr("object")
results <- condition(object)
stopifnot(is.expectation(results))
exp <- condition(object)
stopifnot(is.expectation(exp))

results$srcref <- find_test_srcref()
exp <- update_expectation(exp, srcref = find_test_srcref(), info = info,
label = label)

if (!is.null(label)) {
results$failure_msg <- paste0(label, " ", results$failure_msg)
}

if (!is.null(info)) {
results$failure_msg <- paste0(results$failure_msg, "\n", info)
}

get_reporter()$add_result(results)
invisible(results)
expect(exp)
}

# find the srcref of the test call, or NULL
Expand Down Expand Up @@ -79,9 +71,8 @@ find_test_srcref <- function() {
#' test_that("this test fails", fail())
#' }
fail <- function(message = "Failure has been forced") {
results <- expectation(FALSE, message)
get_reporter()$add_result(results)
invisible()
exp <- expectation(FALSE, message)
expect(exp)
}


Expand All @@ -94,9 +85,8 @@ fail <- function(message = "Failure has been forced") {
#' test_that("this test fails", fail())
#' }
succeed <- function(message = "Success has been forced") {
results <- expectation(TRUE, message)
get_reporter()$add_result(results)
invisible()
exp <- expectation(TRUE, message)
expect(exp)
}

#' Negate an expectation
Expand Down
96 changes: 70 additions & 26 deletions R/expectation.r
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,76 @@
#' @keywords internal
#' @export
expectation <- function(passed, failure_msg, srcref = NULL) {
structure(
new_expectation(passed = passed, failure_msg = failure_msg,
srcref = srcref)
}

new_expectation <- function(failure_msg, srcref, ...,
passed = FALSE, error = FALSE, skipped = FALSE) {
if (passed) {
Copy link
Member

Choose a reason for hiding this comment

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

passed && !error?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe it would be better to have type = c("error", "failure", "skip", "warning", "message") etc?

Copy link
Member Author

Choose a reason for hiding this comment

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

If error, then passed is FALSE by construction (although not enforced). I agree that a type is better, but I'd like to deal with this in a new batch of changes, and also change the reporters -- this PR is already big enough.

Copy link
Member

Choose a reason for hiding this comment

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

Sure. I think I just introduced some merge conflicts, but if you squash this, I'll fix.

class = c("expectation", "condition")
} else {
class = c("expectation", "error", "condition")
}

exp <- structure(
list(
passed = passed,
error = FALSE,
skipped = FALSE,
failure_msg = failure_msg,
srcref = srcref
error = error,
skipped = skipped,
failure_msg = failure_msg
),
class = "expectation"
class = class
)

update_expectation(exp, srcref)
}

update_expectation <- function(exp, srcref, info = NULL, label = NULL) {
Copy link
Member

Choose a reason for hiding this comment

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

It feels like we should be able to eventually eliminate this function, wrapping it into the constructor

Copy link
Member Author

Choose a reason for hiding this comment

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

Difficult without finally deprecating info and label arguments (#218).

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, lets look again once we've eliminated those args and refactored the expectations

exp$srcref <- srcref

if (!is.null(label)) {
exp$failure_msg <- paste0(label, " ", exp$failure_msg)
}

if (!is.null(info)) {
exp$failure_msg <- paste0(exp$failure_msg, "\n", info)
}

# TODO: Get rid of failure_msg in favor of message
exp$message <- exp$failure_msg

exp
}

expectation_error <- function(error, calls) {
expectation_ok <- function(exp) {
isTRUE(exp$passed)
}


as.expectation <- function(x, ...) UseMethod("as.expectation", x)

#' @export
as.expectation.default <- function(x, ...) {
stop("Don't know how to convert '", paste(class(x), collapse = "', '"),
"' to expectation.", call. = FALSE)
}

#' @export
as.expectation.expectation <- function(x, ...) x

#' @export
as.expectation.logical <- function(x, message, ...) {
expectation(passed = x, failure_msg = message, srcref = find_test_srcref())
}

#' @export
as.expectation.error <- function(x, ...) {
error <- x$message
calls <- x$calls
# TODO: Collect srcref in test_code()
srcref <- x$srcref
Copy link
Member Author

Choose a reason for hiding this comment

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

The srcref handling needs to be improved. This is currently just a placeholder, should be filled later by the calling handler to support file+line reporting.


msg <- gsub("Error.*?: ", "", as.character(error))

if (length(calls) > 0) {
Expand All @@ -35,29 +92,16 @@ expectation_error <- function(error, calls) {
msg <- gsub("\n$", "", msg)
}

structure(
list(
passed = FALSE,
error = TRUE,
skipped = FALSE,
failure_msg = msg
),
class = "expectation"
)
new_expectation(msg, srcref, error = TRUE)
}

expectation_skipped <- function(error) {
#' @export
as.expectation.skip <- function(x, ...) {
error <- x$message
srcref <- x$srcref
msg <- gsub("Error.*?: ", "", as.character(error))

structure(
list(
passed = FALSE,
error = FALSE,
skipped = TRUE,
failure_msg = msg
),
class = "expectation"
)
new_expectation(msg, srcref, skipped = TRUE)
}

#' @export
Expand Down
7 changes: 0 additions & 7 deletions R/expectations-silent.R
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,3 @@ expect_silent <- function(expr) {
paste0(label, " produced ", paste(outputs, collapse = ", "))
)
}

expect <- function(passed, message) {
exp <- expectation(passed, message, srcref = find_test_srcref())
get_reporter()$add_result(exp)

invisible(exp)
}
45 changes: 35 additions & 10 deletions R/test-that.r
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
#' }
test_that <- function(desc, code) {
test_code(desc, substitute(code), env = parent.frame())
invisible()
}


Expand All @@ -45,35 +44,61 @@ test_code <- function(description, code, env) {
get_reporter()$start_test(description)
on.exit(get_reporter()$end_test())

ok <- TRUE
capture_calls <- function(e) {
# Capture call stack, removing last two calls from end (added by
# withCallingHandlers), and first frame + 7 calls from start (added by
# tryCatch etc)
e$calls <- utils::head(sys.calls()[-seq_len(frame + 7)], -2)
signalCondition(e)
}
handle_expectation <- function(exp) {
get_reporter()$add_result(exp)
ok <<- ok && expectation_ok(exp)
invokeRestart(findRestart("continue_test", exp))
}
report_condition <- function(e) {
ok <<- FALSE
get_reporter()$add_result(as.expectation(e))
}

frame <- sys.nframe()

ok <- TRUE
tryCatch(
withCallingHandlers(
eval(code, new_test_environment),
error = capture_calls,
expectation = handle_expectation,
message = function(c) invokeRestart("muffleMessage")
),
error = function(e) {
ok <- FALSE
report <- expectation_error(e$message, e$calls)
get_reporter()$add_result(report)
}, skip = function(e) {
report <- expectation_skipped(e$message)
get_reporter()$add_result(report)
}
error = report_condition,
skip = report_condition
)

invisible(ok)
}


expect <- function(exp, ...) {
exp <- as.expectation(exp, ...)

withRestarts(
raise_condition(exp),
continue_test = function(e) NULL
)

invisible(exp)
}

raise_condition <- function(exp) {
if (expectation_ok(exp)) {
signalCondition(exp)
} else {
stop(exp)
}
}


#' R package to make testing fun!
#'
#' Try the example below. Have a look at the references and learn more
Expand Down
3 changes: 3 additions & 0 deletions tests/testthat/test-bare.r
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ expect_gte(3, 3)
expect_less_than(2, 3)
expect_lt(2, 3)
expect_lte(2, 2)

expect_error(expect_false(FALSE), NA)
Copy link
Member

Choose a reason for hiding this comment

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

I think you accidentally re-added this?

Copy link
Member Author

Choose a reason for hiding this comment

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

This was intentional, the commented code that was below is now gone.

expect_error(expect_false(TRUE))
22 changes: 22 additions & 0 deletions tests/testthat/test-test-that.r
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,25 @@ test_that("line numbers are found and given to reporters", {
"
expect_equal(.test_and_fetch_lines(code), 3)
})

test_that("return value from test_that", {
with_reporter("", success <- test_that("success", {}))
expect_true(success)
with_reporter("", success <- test_that("success", succeed()))
expect_true(success)
with_reporter("", success <- test_that("success", expect(TRUE, "Yes!")))
expect_true(success)

with_reporter("", error <- test_that("error", barf))
expect_false(error)

with_reporter("", failure <- test_that("failure", expect_true(FALSE)))
expect_false(failure)
with_reporter("", failure <- test_that("failure", fail()))
expect_false(failure)
with_reporter("", success <- test_that("failure", expect(FALSE, "No!")))
expect_false(failure)

with_reporter("", skip <- test_that("skip", skip("skipping")))
expect_false(skip)
})