From e372180eed9a5b04d4dae9d8d7420cd9d27ebf66 Mon Sep 17 00:00:00 2001 From: 0xtch Date: Fri, 14 Aug 2026 15:55:54 +0200 Subject: [PATCH 1/5] test() now errors on unexpected output --- R/test.data.table.R | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/R/test.data.table.R b/R/test.data.table.R index d78b215988..595d51074c 100644 --- a/R/test.data.table.R +++ b/R/test.data.table.R @@ -538,16 +538,11 @@ test = function(num, x, y=TRUE, ..., old_options = do.call(base::options, as.list(options)) # as.list(): allow passing named character vector for convenience on.exit(base::options(old_options), add=TRUE) } - if (is.null(output) && is.null(notOutput)) { - x = suppressMessages(withCallingHandlers(tryCatch(x, error=eHandler), warning=wHandler, message=mHandler)) - # save the overhead of capture.output() since there are a lot of tests, often called in loops + out = if (is.null(output) && is.null(notOutput) || xsub %iscall% "print") { + capture.output(x <- suppressMessages(withCallingHandlers(tryCatch(x, error=eHandler), warning=wHandler, message=mHandler))) # Thanks to tryCatch2 by Jan here : https://github.com/jangorecki/logR/blob/master/R/logR.R#L21 } else { - out = if (xsub %iscall% "print") { - capture.output(x <- suppressMessages(withCallingHandlers(tryCatch(x, error=eHandler), warning=wHandler, message=mHandler))) - } else { - capture.output(print(x <- suppressMessages(withCallingHandlers(tryCatch(x, error=eHandler), warning=wHandler, message=mHandler)))) - } + capture.output(print(x <- suppressMessages(withCallingHandlers(tryCatch(x, error=eHandler), warning=wHandler, message=mHandler)))) } if (!is.null(options)) { # some of the options passed to test() may break internal data.table use below (e.g. invalid datatable.alloccol), so undo them ASAP @@ -600,12 +595,19 @@ test = function(num, x, y=TRUE, ..., } } } - if (fail && exists("out",inherits=FALSE)) { + if (fail && length(out)) { # nocov start catf("Output captured before unexpected warning/error/message:\n") writeLines(out) # nocov end } + if (!fail && is.null(output) && is.null(notOutput) && length(out)) { + # nocov start + catf("Test %s produced unexpected output:\n", numStr) + writeLines(out) + fail = TRUE + # nocov end + } if (!fail && !length(error) && (length(output) || length(notOutput))) { if (out[length(out)] == "NULL") out = out[-length(out)] out = paste(out, collapse="\n") From 7159ea2ad14ae1f3dcf873e6eb8d24cf79034c8c Mon Sep 17 00:00:00 2001 From: 0xtch Date: Fri, 14 Aug 2026 21:33:46 +0200 Subject: [PATCH 2/5] Don't flag verbose diagnostics as unexpected output --- R/test.data.table.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/test.data.table.R b/R/test.data.table.R index 595d51074c..109aa06c1a 100644 --- a/R/test.data.table.R +++ b/R/test.data.table.R @@ -601,7 +601,7 @@ test = function(num, x, y=TRUE, ..., writeLines(out) # nocov end } - if (!fail && is.null(output) && is.null(notOutput) && length(out)) { + if (!fail && is.null(output) && is.null(notOutput) && length(out) && !isTRUE(getOption("datatable.verbose"))) { # nocov start catf("Test %s produced unexpected output:\n", numStr) writeLines(out) From 031350a0a3b7e49f59645449f15d510dff416bf0 Mon Sep 17 00:00:00 2001 From: 0xtch Date: Fri, 14 Aug 2026 23:24:55 +0200 Subject: [PATCH 3/5] Add tests and documentation --- inst/tests/self.Rraw | 8 ++++++++ man/test.Rd | 2 ++ 2 files changed, 10 insertions(+) diff --git a/inst/tests/self.Rraw b/inst/tests/self.Rraw index b7eb7ed1d1..1f4941aa06 100644 --- a/inst/tests/self.Rraw +++ b/inst/tests/self.Rraw @@ -12,3 +12,11 @@ test(1.1, test(0, warning("a"), warning=c("a", "b"), check_value=FALSE), FALSE, output="Test 0 produced 1 warnings but expected 2\nExpected: a\n b\nObserved: a") test(1.2, test(0, {warning("a"); warning("b")}, warning="a", check_value=FALSE), FALSE, output="Test 0 produced 2 warnings but expected 1\nExpected: a\nObserved: a\n b") + +# test() catches unexpected console output, #7847 +test(2.1, test(0, {cat("123\n"); TRUE}), FALSE, + output="Test 0 produced unexpected output:\n123") +# output printed by verbose mode itself doesn't count as unexpected, #7847 +test(2.2, test(0, data.table(a = 1:3)[, sum(a)], 6L), + options=c(datatable.verbose=TRUE), + notOutput="Detected that j") \ No newline at end of file diff --git a/man/test.Rd b/man/test.Rd index fa8693f588..2f0881dc9a 100644 --- a/man/test.Rd +++ b/man/test.Rd @@ -37,6 +37,8 @@ test(num, x, y = TRUE, ..., Multiple warnings are supported; supply a vector of strings to \code{warning=}. If \code{x} does not produce the correct number of warnings in the correct order, the test will fail. + Unless \code{output=} or \code{notOutput=} is supplied, you are automatically asserting that \code{x} prints nothing to the console; the test will fail if it does. This does not apply when \code{datatable.verbose} is \code{TRUE}. + Strings passed to \code{notOutput=} should be minimal; e.g. pick out single words from the output that you desire to check does not occur. The reason being so that the test does not incorrectly pass just because the output has slightly changed. For example \code{notOutput="revised"} is better than \code{notOutput="revised flag to true"}. \code{notOutput=} is automatically case insensitive for this reason. } \value{ From 5a88f506b2060e5f653ebbccc7f5bd2a738a8cef Mon Sep 17 00:00:00 2001 From: 0xtch Date: Sat, 15 Aug 2026 00:12:15 +0200 Subject: [PATCH 4/5] Update NEWS.md --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 6756f1edd2..750718c5cd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -116,6 +116,8 @@ 11. Handled OpenMP deprecation of `master` construct, [#7882](https://github.com/Rdatatable/data.table/pull/7882). Thanks @TimTaylor for the PR. +12. `test()` now fails on unexpected console output, with the message `Test produced unexpected output`, consistent with how unexpected warnings and messages are already handled, [#7847](https://github.com/Rdatatable/data.table/issues/7847). Thanks @MichaelChirico for the suggestion and @0xtch for the implementation. + ## data.table [v1.18.4](https://github.com/Rdatatable/data.table/milestone/45) (6 May 2026) ### BUG FIXES From 9936090a36c64ea73ad983fa59e7d2ac6e584d79 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Sat, 15 Aug 2026 23:28:32 -0700 Subject: [PATCH 5/5] trailing \n --- inst/tests/self.Rraw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/tests/self.Rraw b/inst/tests/self.Rraw index 1f4941aa06..6082ae252c 100644 --- a/inst/tests/self.Rraw +++ b/inst/tests/self.Rraw @@ -19,4 +19,4 @@ test(2.1, test(0, {cat("123\n"); TRUE}), FALSE, # output printed by verbose mode itself doesn't count as unexpected, #7847 test(2.2, test(0, data.table(a = 1:3)[, sum(a)], 6L), options=c(datatable.verbose=TRUE), - notOutput="Detected that j") \ No newline at end of file + notOutput="Detected that j")