diff --git a/news/changelog-1.6.md b/news/changelog-1.6.md index cd3f508dc2c..d6f820934fb 100644 --- a/news/changelog-1.6.md +++ b/news/changelog-1.6.md @@ -71,6 +71,7 @@ All changes included in 1.6: ## Projects - ([#10268](https://github.com/quarto-dev/quarto-cli/issues/10268)): `quarto create` supports opening project in Positron, in addition to VS Code and RStudio IDE. +- ([#10566](https://github.com/quarto-dev/quarto-cli/issues/10566)): Ensure that `quarto run` outputs `stdout` and `stderr` to the correct streams. ### Websites diff --git a/src/core/run/lua.ts b/src/core/run/lua.ts index 6c5fbdf70b9..d9e797ed274 100644 --- a/src/core/run/lua.ts +++ b/src/core/run/lua.ts @@ -42,10 +42,16 @@ export const luaRunHandler: RunHandler = { ); cmd.push(...args); - return await execProcess({ - cmd, - ...options, - }, ""); + return await execProcess( + { + cmd, + ...options, + }, + "", + undefined, + undefined, + true, + ); }, }; diff --git a/src/core/run/python.ts b/src/core/run/python.ts index 3770adf166b..1fda17f5fb9 100644 --- a/src/core/run/python.ts +++ b/src/core/run/python.ts @@ -1,9 +1,8 @@ /* -* python.ts -* -* Copyright (C) 2020-2022 Posit Software, PBC -* -*/ + * python.ts + * + * Copyright (C) 2020-2022 Posit Software, PBC + */ import { extname } from "../../deno_ral/path.ts"; import { pythonExec } from "../jupyter/exec.ts"; @@ -21,13 +20,19 @@ export const pythonRunHandler: RunHandler = { stdin?: string, options?: RunHandlerOptions, ) => { - return await execProcess({ - cmd: [ - ...(await pythonExec()), - script, - ...args, - ], - ...options, - }, stdin); + return await execProcess( + { + cmd: [ + ...(await pythonExec()), + script, + ...args, + ], + ...options, + }, + stdin, + undefined, + undefined, + true, + ); }, }; diff --git a/src/core/run/r.ts b/src/core/run/r.ts index 98b3eec47be..185bfe1a15d 100644 --- a/src/core/run/r.ts +++ b/src/core/run/r.ts @@ -1,9 +1,8 @@ /* -* r.ts -* -* Copyright (C) 2020-2022 Posit Software, PBC -* -*/ + * r.ts + * + * Copyright (C) 2020-2022 Posit Software, PBC + */ import { extname } from "../../deno_ral/path.ts"; @@ -21,13 +20,19 @@ export const rRunHandler: RunHandler = { stdin?: string, options?: RunHandlerOptions, ) => { - return await execProcess({ - cmd: [ - await rBinaryPath("Rscript"), - script, - ...args, - ], - ...options, - }, stdin); + return await execProcess( + { + cmd: [ + await rBinaryPath("Rscript"), + script, + ...args, + ], + ...options, + }, + stdin, + undefined, + undefined, + true, + ); }, }; diff --git a/tests/smoke/run/run-script.test.ts b/tests/smoke/run/run-script.test.ts index 0d62a71c8a8..2a7ab6ee943 100644 --- a/tests/smoke/run/run-script.test.ts +++ b/tests/smoke/run/run-script.test.ts @@ -1,14 +1,53 @@ import { basename, join } from "../../../src/deno_ral/path.ts"; import { ensureDirSync } from "../../../src/deno_ral/fs.ts"; -import { assert } from "testing/asserts"; +import { assert, assertEquals } from "testing/asserts"; import { execProcess } from "../../../src/core/process.ts"; import { quartoDevCmd } from "../../utils.ts"; import { unitTest } from "../../test.ts"; +import { EOL } from "fs/eol"; +import { lines } from "../../../src/core/text.ts"; const workingDir = Deno.makeTempDirSync(); ensureDirSync(workingDir); +const ensureStreams = (name: string, script: string, stdout: string, stderr: string) => { + unitTest(name, async () => { + const result = await execProcess({ + cmd: [ + quartoDevCmd(), + "run", + basename(script), + ], + // disable logging here to allow for checking the output + env: { + "QUARTO_LOG_LEVEL": "CRITICAL", + } + }, + undefined, + undefined, + undefined, + true + ); + assert(result.success); + assertEquals((result.stdout ?? "").replaceAll("\r", ""), stdout); + assertEquals((result.stderr ?? "").replaceAll("\r", ""), stderr); + }, + { + teardown: () => { + try { + Deno.removeSync(basename(script)); + } catch (_e) { + // ignore + } + return Promise.resolve(); + }, + cwd: () => { + return workingDir; + } + }) +} + const testRunCmd = (name: string, script: string) => { unitTest(name, async () => { const result = await execProcess({ @@ -16,7 +55,7 @@ const testRunCmd = (name: string, script: string) => { quartoDevCmd(), "run", basename(script), - ], + ] }); assert(result.success); }, @@ -53,4 +92,26 @@ testRunCmd("run-py-script", pyScript); // Run R script const rScript = join(workingDir, "test.R"); Deno.writeTextFileSync(rScript, "print('Hello, world!')"); -testRunCmd("run-r-script", rScript); \ No newline at end of file +testRunCmd("run-r-script", rScript); + +// check stream outputs + +// in R +const rScript2 = join(workingDir, "test2.R"); +Deno.writeTextFileSync(rScript2, "cat('write stdout\\n', file = stdout()); cat('write stderr\\n', file = stderr())"); +ensureStreams("run-r-script-stdout-stderr", rScript2, "write stdout\n", "write stderr\n"); + +// in Python +const pyScript2 = join(workingDir, "test2.py"); +Deno.writeTextFileSync(pyScript2, "import sys; print('write stdout'); print('write stderr', file = sys.stderr)"); +ensureStreams("run-py-script-stdout-stderr", pyScript2, "write stdout\n", "write stderr\n"); + +// in Deno TS +const tsScript2 = join(workingDir, "test2.ts"); +Deno.writeTextFileSync(tsScript2, "console.log('write stdout'); console.error('write stderr')"); +ensureStreams("run-ts-script-stdout-stderr", tsScript2, "write stdout\n", "write stderr\n"); + +// in Lua +const luaScript2 = join(workingDir, "test2.lua"); +Deno.writeTextFileSync(luaScript2, "print('write stdout'); io.stderr:write('write stderr\\n')"); +ensureStreams("run-lua-script-stdout-stderr", luaScript2, "write stdout\n\n", "write stderr\n"); // don't know why there is an extra newline \ No newline at end of file