From b8c68e11f7bebfc89ae9b83b0179740344c08b7a 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 a1d46ae4affed91c8465340a7a5d71559a7e1e29 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 8a04b23eb177c2c6dc9e8d25bd2eb94a88ab109e 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 e3a58cead8b20c55294364e36e6468434ae46b03 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 5d2c35aa9a..6d22ac5279 100644 --- a/NEWS.md +++ b/NEWS.md @@ -112,6 +112,8 @@ 10. `fwrite()` returns a clearer error message when `na = data.frame()` is used, [#7866](https://github.com/Rdatatable/data.table/issues/7866). Thanks @mcol for the report and the fix. +11. `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 4a8b1ff6c1d66986024397bde44e234e097b8712 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")