diff --git a/src/execute/rmd.ts b/src/execute/rmd.ts index 3cbfd02e2b..3d6f39c052 100644 --- a/src/execute/rmd.ts +++ b/src/execute/rmd.ts @@ -500,50 +500,63 @@ function withinActiveRenv() { } } -async function printCallRDiagnostics() { - const rBin = await checkRBinary(); - if (rBin === undefined) { - info(""); - info(rInstallationMessage()); - info(""); - } else { - const caps = await knitrCapabilities(rBin); - if (caps === undefined) { - info( - `Problem with running R found at ${rBin} to check environment configurations.`, - ); - info("Please check your installation of R."); +export async function printCallRDiagnostics( + checkRBinaryImpl: () => Promise = checkRBinary, +) { + // This re-enters R discovery to explain a callR failure. A throw here + // (e.g. from checkRBinaryImpl/rBinaryPath) must never replace the + // original callR error the caller is already reporting. + try { + const rBin = await checkRBinaryImpl(); + if (rBin === undefined) { + info(""); + info(rInstallationMessage()); info(""); } else { - if ( - !caps?.packages.rmarkdown || !caps?.packages.knitr || - !caps?.packages.knitrVersOk || !caps?.packages.rmarkdownVersOk - ) { - info("R installation:"); - info(knitrCapabilitiesMessage(caps, " ")); - if (!!!caps?.packages.knitr || !caps?.packages.knitrVersOk) { - info(""); - info( - knitrInstallationMessage( - "", - "knitr", - !!caps.packages.knitr && !caps.packages.knitrVersOk, - ), - ); - } - if (!!!caps?.packages.rmarkdown || !caps?.packages.rmarkdownVersOk) { + const caps = await knitrCapabilities(rBin); + if (caps === undefined) { + info( + `Problem with running R found at ${rBin} to check environment configurations.`, + ); + info("Please check your installation of R."); + info(""); + } else { + if ( + !caps?.packages.rmarkdown || !caps?.packages.knitr || + !caps?.packages.knitrVersOk || !caps?.packages.rmarkdownVersOk + ) { + info("R installation:"); + info(knitrCapabilitiesMessage(caps, " ")); + if (!!!caps?.packages.knitr || !caps?.packages.knitrVersOk) { + info(""); + info( + knitrInstallationMessage( + "", + "knitr", + !!caps.packages.knitr && !caps.packages.knitrVersOk, + ), + ); + } + if (!!!caps?.packages.rmarkdown || !caps?.packages.rmarkdownVersOk) { + info(""); + info( + knitrInstallationMessage( + "", + "rmarkdown", + !!caps?.packages.rmarkdown && !caps?.packages.rmarkdownVersOk, + ), + ); + } info(""); - info( - knitrInstallationMessage( - "", - "rmarkdown", - !!caps?.packages.rmarkdown && !caps?.packages.rmarkdownVersOk, - ), - ); } - info(""); } } + } catch (e) { + warning( + `Unable to gather R diagnostics: ${ + e instanceof Error ? e.message : String(e) + }`, + ); } } diff --git a/tests/unit/print-call-r-diagnostics.test.ts b/tests/unit/print-call-r-diagnostics.test.ts new file mode 100644 index 0000000000..b7d344ae37 --- /dev/null +++ b/tests/unit/print-call-r-diagnostics.test.ts @@ -0,0 +1,31 @@ +/* + * print-call-r-diagnostics.test.ts + * + * Tests that a failure while gathering R diagnostics (e.g. checkRBinary + * throwing) never masks the original callR error. + * See https://github.com/quarto-dev/quarto-cli/issues/14775 + * + * Copyright (C) 2026 Posit Software, PBC + */ + +import { unitTest } from "../test.ts"; +import { assert } from "testing/asserts"; +import { printCallRDiagnostics } from "../../src/execute/rmd.ts"; + +unitTest( + "printCallRDiagnostics - swallows a throw from R discovery", + async () => { + let threw = false; + try { + await printCallRDiagnostics(() => { + throw new Error("boom from checkRBinary"); + }); + } catch { + threw = true; + } + assert( + !threw, + "printCallRDiagnostics should not propagate a failure from R discovery", + ); + }, +);