From acc33e7c16da4954520064c8fe520b19402ade8e Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 16 Jul 2026 16:38:59 +0200 Subject: [PATCH 1/4] Fix run-tests.sh silently truncating recursive ** glob buckets Bash only expands ** recursively when globstar is enabled, and run-tests.sh passed its resolved TESTS_TO_RUN unquoted to the final deno invocation, letting bash glob-expand it with globstar off. A bucket like qmd-files/**/*.qmd matched only files exactly one directory level deep, silently dropping anything nested further - which is why the ff-matrix CI job ran 10 tests on ubuntu-latest against 271 on windows-latest for the same bucket (run-tests.ps1 never shell-expands the pattern, deferring to Deno's own recursive glob support in smoke-all.test.ts). A prior attempt to fix this (9d718d111) added `shopt -s globstar` to the CI wrapper around the run-tests.sh call, but that only affects the wrapper step's own shell - run-tests.sh runs as a separate bash process with its own default shell-option state, so the setting never reached the actual expansion. Convert TESTS_TO_RUN and SMOKE_ALL_FILES to bash arrays and quote them at the deno invocation so bash never expands the pattern itself; a literal glob string now reaches smoke-all.test.ts's own expandGlobSync, matching the working Windows code path instead of relying on a second, divergent glob engine. Also drops the now-dead `shopt -s globstar` from the CI wrapper. Adds tests/run-tests-recursive-glob.test.sh, a standalone regression test that runs the real run-tests.sh against a nested qmd fixture with a recorder-stub deno binary, confirming a depth-2 file survives a ** bucket. --- .github/workflows/test-smokes.yml | 2 +- tests/run-tests-recursive-glob.test.sh | 95 ++++++++++++++++++++++++++ tests/run-tests.sh | 25 ++++--- 3 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 tests/run-tests-recursive-glob.test.sh diff --git a/.github/workflows/test-smokes.yml b/.github/workflows/test-smokes.yml index 074b84ecb39..652807eddd7 100644 --- a/.github/workflows/test-smokes.yml +++ b/.github/workflows/test-smokes.yml @@ -317,7 +317,7 @@ jobs: echo ">>> ./run-tests.sh ${file}" # Run tests without -e so we don't exit on first failure set +e - shopt -s globstar && ./run-tests.sh "$file" + ./run-tests.sh "$file" status=$? set -e echo "::endgroup::" diff --git a/tests/run-tests-recursive-glob.test.sh b/tests/run-tests-recursive-glob.test.sh new file mode 100644 index 00000000000..4370468a24b --- /dev/null +++ b/tests/run-tests-recursive-glob.test.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# Regression test for run-tests.sh silently truncating recursive ** glob +# buckets on Linux/macOS (quarto-cli-jho3). +# +# Runs the real run-tests.sh against a nested qmd fixture, with the deno +# binary swapped for a recorder stub (via the DENO_DIR override run-tests.sh +# already supports), and checks that a file nested two directories deep +# under a `**` bucket still reaches the resolved test-file set. The +# assertion re-expands any still-glob-shaped token itself (mirroring what +# smoke-all.test.ts's expandGlobSync does), so it passes regardless of +# whether the fix quotes the pattern through to deno or expands it in bash. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." >/dev/null 2>&1 && pwd)" + +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +fail() { + echo "FAIL: $1" + exit 1 +} + +# --- Isolated fake repo skeleton, just enough for run-tests.sh to run --- +mkdir -p "$TMP/tests" +mkdir -p "$TMP/package/scripts/common" +mkdir -p "$TMP/package/dist/bin/tools/stub" +mkdir -p "$TMP/src/resources" + +# Strip CR in case this checkout has core.autocrlf=true (Windows dev boxes); +# CI checkouts on Linux/macOS never have CRLF here, this is a local-copy +# normalization only, not part of the fix under test. +tr -d '\r' < "$REPO_ROOT/tests/run-tests.sh" > "$TMP/tests/run-tests.sh" +tr -d '\r' < "$REPO_ROOT/package/scripts/common/utils.sh" > "$TMP/package/scripts/common/utils.sh" +chmod +x "$TMP/tests/run-tests.sh" + +RECORD_FILE="$TMP/recorded-argv.txt" +cat > "$TMP/package/dist/bin/tools/stub/deno" <<'EOF' +#!/usr/bin/env bash +printf '%s\n' "$@" > "$RECORD_FILE" +exit 0 +EOF +chmod +x "$TMP/package/dist/bin/tools/stub/deno" + +# --- Nested fixture: one .qmd at depth 1, one at depth 2 under qmd-files/ --- +mkdir -p "$TMP/qmd-files/a/b" +: > "$TMP/qmd-files/a/one.qmd" +: > "$TMP/qmd-files/a/b/two.qmd" + +# --- Invoke the real script exactly as test-smokes.yml's bucket runner does --- +( + cd "$TMP/tests" || exit 1 + export QUARTO_TESTS_NO_CONFIG=true + export QUARTO_TESTS_FORCE_NO_VENV=true + export DENO_DIR=stub + export RECORD_FILE + ./run-tests.sh "../qmd-files/**/*.qmd" +) + +[ -f "$RECORD_FILE" ] || fail "deno stub was never invoked - run-tests.sh did not reach the final deno call" + +mapfile -t recorded < "$RECORD_FILE" + +dashdash_index=-1 +for i in "${!recorded[@]}"; do + if [ "${recorded[$i]}" = "--" ]; then + dashdash_index=$i + break + fi +done +[ "$dashdash_index" -ge 0 ] || fail "no isolated '--' token in deno argv (got: ${recorded[*]})" + +# Resolve the file tokens after '--' into an actual file set, expanding any +# token that still looks like a glob pattern (mirrors expandGlobSync). +resolved=() +( + cd "$TMP/tests" || exit 1 + shopt -s globstar nullglob + for ((i = dashdash_index + 1; i < ${#recorded[@]}; i++)); do + tok="${recorded[$i]}" + if [[ "$tok" == *"*"* ]]; then + for f in $tok; do + echo "$f" + done + else + echo "$tok" + fi + done +) > "$TMP/resolved-files.txt" + +grep -q 'a/one\.qmd$' "$TMP/resolved-files.txt" || fail "depth-1 fixture file (qmd-files/a/one.qmd) missing from resolved test set" +grep -q 'a/b/two\.qmd$' "$TMP/resolved-files.txt" || fail "depth-2 fixture file (qmd-files/a/b/two.qmd) missing from resolved test set - recursive ** bucket was silently truncated" + +echo "PASS: recursive ** bucket resolved both depth-1 and depth-2 fixture files" diff --git a/tests/run-tests.sh b/tests/run-tests.sh index 702b70b8414..851876042c8 100755 --- a/tests/run-tests.sh +++ b/tests/run-tests.sh @@ -130,19 +130,19 @@ else ## Short version syntax to run smoke-all.test.ts ## Only use if different than ./run-test.sh ./smoke/smoke-all.test.ts if [[ "$1" =~ smoke-all\.test\.ts ]]; then - TESTS_TO_RUN=$@ + TESTS_TO_RUN=("$@") else # Check file argument - SMOKE_ALL_FILES="" - TESTS_TO_RUN="" + SMOKE_ALL_FILES=() + TESTS_TO_RUN=() if [[ ! -z "$*" ]]; then for file in "$*"; do filename=$(basename "$file") # smoke-all.test.ts works with .qmd, .md and .ipynb but will ignored file starting with _ if [[ $filename =~ ^[^_].*[.]qmd$ ]] || [[ $filename =~ ^[^_].*[.]ipynb$ ]] || [[ $filename =~ ^[^_].*[.]md$ ]]; then - SMOKE_ALL_FILES="${SMOKE_ALL_FILES} ${file}" + SMOKE_ALL_FILES+=("${file}") elif [[ $file =~ .*[.]ts$ ]]; then - TESTS_TO_RUN="${TESTS_TO_RUN} ${file}" + TESTS_TO_RUN+=("${file}") else echo "#### WARNING" echo "Only .ts, or .qmd, .md and .ipynb passed to smoke-all.test.ts are accepted (file starting with _ are ignored)." @@ -151,17 +151,22 @@ else fi done fi - if [ "$SMOKE_ALL_FILES" != "" ]; then - if [ "$TESTS_TO_RUN" != "" ]; then + if [ "${#SMOKE_ALL_FILES[@]}" -ne 0 ]; then + if [ "${#TESTS_TO_RUN[@]}" -ne 0 ]; then echo "#### WARNING" echo "When passing .qmd, .md and/or .ipynb, only ./smoke/smoke-all.test.ts will be run. Other tests files are ignored." - echo "Ignoring ${TESTS_TO_RUN}." + echo "Ignoring ${TESTS_TO_RUN[*]}." echo "####" fi - TESTS_TO_RUN="${SMOKE_ALL_TEST_FILE} -- ${SMOKE_ALL_FILES}" + TESTS_TO_RUN=("${SMOKE_ALL_TEST_FILE}" "--" "${SMOKE_ALL_FILES[@]}") fi fi - "${QUARTO_BIN_PATH}/tools/${DENO_ARCH_DIR}/deno" test ${QUARTO_DENO_OPTIONS} --check ${QUARTO_DENO_EXTRA_OPTIONS} "${QUARTO_IMPORT_MAP_ARG}" $TESTS_TO_RUN + # TESTS_TO_RUN is an array and quoted here on purpose: a bucket can be a + # literal, unexpanded ** glob pattern (e.g. from the ff-matrix CI bucket), + # and smoke-all.test.ts expands it itself via expandGlobSync. Expanding it + # here instead would depend on bash's own (non-recursive by default) glob + # semantics and could silently drop deeply nested matches. + "${QUARTO_BIN_PATH}/tools/${DENO_ARCH_DIR}/deno" test ${QUARTO_DENO_OPTIONS} --check ${QUARTO_DENO_EXTRA_OPTIONS} "${QUARTO_IMPORT_MAP_ARG}" "${TESTS_TO_RUN[@]}" SUCCESS=$? fi From a97e2ccf7975bf97abcc8a1e95ad1cafaa78d6c3 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 16 Jul 2026 18:12:33 +0200 Subject: [PATCH 2/4] Ignore feature-format-matrix render byproducts (.typ, .quarto_ipynb) Running the full qmd-files tree locally (as now happens in CI after the run-tests.sh recursive-glob fix) leaves keep-typ and ipynb-cache byproducts untracked and unignored. --- dev-docs/feature-format-matrix/.gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dev-docs/feature-format-matrix/.gitignore b/dev-docs/feature-format-matrix/.gitignore index 58d8cfd09bf..36cda2a8fe4 100644 --- a/dev-docs/feature-format-matrix/.gitignore +++ b/dev-docs/feature-format-matrix/.gitignore @@ -5,5 +5,8 @@ qmd-files/**/*.html qmd-files/**/*.pdf qmd-files/**/*.md qmd-files/**/*.mdx +qmd-files/**/*.typ /.quarto/ + +**/*.quarto_ipynb From cf9ecffd81a9f7aaaf7e542920b40dd6730028c3 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 16 Jul 2026 18:13:24 +0200 Subject: [PATCH 3/4] Fix host-dependent font in feature-format-matrix typst smoke test Georgia is a Windows/macOS-bundled font absent on Ubuntu CI runners. Typst CSS font-family filtering (#12556) silently drops unavailable fonts, so the same document rendered `("Georgia", "serif")` on Windows/macOS but `("serif",)` on Ubuntu. This went unnoticed because the fixture never actually ran in CI until the run-tests.sh recursive glob fix. Libertinus Serif ships embedded in the Typst binary, so it is present on every host regardless of installed system fonts. --- .../qmd-files/css-properties/font-family/document.qmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-docs/feature-format-matrix/qmd-files/css-properties/font-family/document.qmd b/dev-docs/feature-format-matrix/qmd-files/css-properties/font-family/document.qmd index c068b1c0420..b21d3aa0247 100644 --- a/dev-docs/feature-format-matrix/qmd-files/css-properties/font-family/document.qmd +++ b/dev-docs/feature-format-matrix/qmd-files/css-properties/font-family/document.qmd @@ -19,12 +19,12 @@ _quarto: typst: ensureTypstFileRegexMatches: - - - '#{set text\(font: \("Georgia", "serif"\)\); table\(' + - '#{set text\(font: \("Libertinus Serif", "serif"\)\); table\(' - [] --- ```{=html} - +
AB
``` From 6e03eb90cdf2508cabe803be2b368a0220ad0ff9 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 16 Jul 2026 18:30:34 +0200 Subject: [PATCH 4/4] Fix run-tests.sh collapsing multiple file arguments into one deno argv token The multi-file loop in run-tests.sh iterated `"$*"` (all positional args joined into one string) instead of `"$@"`, so passing several .test.ts or .qmd files at once - documented in tests/README.md - only ever produced a single glued token. Quoting TESTS_TO_RUN as an array (acc33e7c1) removed the accidental unquoted word-splitting that used to reconstruct the separate args, exposing the bug as a real regression. --- tests/run-tests-multiple-file-args.test.sh | 76 ++++++++++++++++++++++ tests/run-tests.sh | 2 +- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 tests/run-tests-multiple-file-args.test.sh diff --git a/tests/run-tests-multiple-file-args.test.sh b/tests/run-tests-multiple-file-args.test.sh new file mode 100644 index 00000000000..29617ea7572 --- /dev/null +++ b/tests/run-tests-multiple-file-args.test.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Regression test for run-tests.sh collapsing multiple .test.ts file +# arguments into a single glued argv token. +# +# tests/README.md documents passing several .ts files at once, e.g.: +# ./run-tests.sh smoke/extensions/extension-render-doc.test.ts smoke/smoke-all.test.ts +# +# run-tests.sh built TESTS_TO_RUN by looping `for file in "$*"; do`, which +# joins all positional args into one string before iterating, so the loop +# only ever runs once. That single glued string used to reach deno unquoted, +# where bash's own word-splitting happened to reconstruct separate args - +# but that's incidental, not something quoting the resulting array should be +# allowed to depend on. This test runs the real run-tests.sh with two file +# arguments and confirms each survives as its own deno argv token. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." >/dev/null 2>&1 && pwd)" + +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +fail() { + echo "FAIL: $1" + exit 1 +} + +# --- Isolated fake repo skeleton, just enough for run-tests.sh to run --- +mkdir -p "$TMP/tests" +mkdir -p "$TMP/package/scripts/common" +mkdir -p "$TMP/package/dist/bin/tools/stub" +mkdir -p "$TMP/src/resources" + +# Strip CR in case this checkout has core.autocrlf=true (Windows dev boxes); +# CI checkouts on Linux/macOS never have CRLF here, this is a local-copy +# normalization only, not part of the fix under test. +tr -d '\r' < "$REPO_ROOT/tests/run-tests.sh" > "$TMP/tests/run-tests.sh" +tr -d '\r' < "$REPO_ROOT/package/scripts/common/utils.sh" > "$TMP/package/scripts/common/utils.sh" +chmod +x "$TMP/tests/run-tests.sh" + +RECORD_FILE="$TMP/recorded-argv.txt" +cat > "$TMP/package/dist/bin/tools/stub/deno" <<'EOF' +#!/usr/bin/env bash +printf '%s\n' "$@" > "$RECORD_FILE" +exit 0 +EOF +chmod +x "$TMP/package/dist/bin/tools/stub/deno" + +# --- Invoke the real script with two separate .test.ts file arguments --- +( + cd "$TMP/tests" || exit 1 + export QUARTO_TESTS_NO_CONFIG=true + export QUARTO_TESTS_FORCE_NO_VENV=true + export DENO_DIR=stub + export RECORD_FILE + ./run-tests.sh "foo.test.ts" "bar.test.ts" +) + +[ -f "$RECORD_FILE" ] || fail "deno stub was never invoked - run-tests.sh did not reach the final deno call" + +mapfile -t recorded < "$RECORD_FILE" + +found_foo=0 +found_bar=0 +found_glued=0 +for tok in "${recorded[@]}"; do + [ "$tok" = "foo.test.ts" ] && found_foo=1 + [ "$tok" = "bar.test.ts" ] && found_bar=1 + [ "$tok" = "foo.test.ts bar.test.ts" ] && found_glued=1 +done + +[ "$found_glued" -eq 0 ] || fail "foo.test.ts and bar.test.ts were glued into a single argv token - multiple file arguments were collapsed" +[ "$found_foo" -eq 1 ] || fail "foo.test.ts missing from deno argv (got: ${recorded[*]})" +[ "$found_bar" -eq 1 ] || fail "bar.test.ts missing from deno argv (got: ${recorded[*]})" + +echo "PASS: multiple .test.ts file arguments reached deno as separate argv tokens" diff --git a/tests/run-tests.sh b/tests/run-tests.sh index 851876042c8..a2c134fd392 100755 --- a/tests/run-tests.sh +++ b/tests/run-tests.sh @@ -136,7 +136,7 @@ else SMOKE_ALL_FILES=() TESTS_TO_RUN=() if [[ ! -z "$*" ]]; then - for file in "$*"; do + for file in "$@"; do filename=$(basename "$file") # smoke-all.test.ts works with .qmd, .md and .ipynb but will ignored file starting with _ if [[ $filename =~ ^[^_].*[.]qmd$ ]] || [[ $filename =~ ^[^_].*[.]ipynb$ ]] || [[ $filename =~ ^[^_].*[.]md$ ]]; then