From 98529e0456b9a63d482c71fb9b5f73ea1d3c4eee Mon Sep 17 00:00:00 2001 From: anandgupta42 <93243293+anandgupta42@users.noreply.github.com> Date: Mon, 7 Sep 2026 14:14:07 -0700 Subject: [PATCH 1/6] fix(ci): validate cached dbt e2e environments; pin the uv venv to the system Python The dbt-tools E2E job on main fails with ENOENT executing test/.dbt-resolve-envs/uv/.venv/bin/dbt: the restored cache holds a uv venv whose bin/python links to a uv-managed interpreter outside the cached directory, absent on a fresh runner. setup-resolve.sh trusted the .done marker and skipped setup, and actions/cache never re-saves on a hit, so the broken environment came back on every run. - setup-resolve.sh: a cached environment counts only if its dbt --version runs (with a timeout); otherwise it is rebuilt. Applied to every scenario. - uv venv is created with --python "$REAL_PYTHON" so it links to an interpreter that exists on every runner. - ci.yml: cache key bumped to -v2 so the broken v1 cache is not restored. Closes #1257 Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci.yml | 5 ++- packages/dbt-tools/test/e2e/setup-resolve.sh | 41 ++++++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01e360cfd0..aff5eda95f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -583,7 +583,10 @@ jobs: uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: packages/dbt-tools/test/.dbt-resolve-envs - key: dbt-resolve-envs-${{ runner.os }}-v1 + # v2: the v1 cache held a uv venv linked to a uv-managed Python that + # fresh runners lack (bin/dbt present, exec ENOENT). setup-resolve.sh + # now validates cached envs and pins uv to the system interpreter. + key: dbt-resolve-envs-${{ runner.os }}-v2 - name: Set up dbt versions run: ./test/e2e/setup-versions.sh 1.8 1.10 1.11 diff --git a/packages/dbt-tools/test/e2e/setup-resolve.sh b/packages/dbt-tools/test/e2e/setup-resolve.sh index 111c6eff24..1b1ad8c239 100755 --- a/packages/dbt-tools/test/e2e/setup-resolve.sh +++ b/packages/dbt-tools/test/e2e/setup-resolve.sh @@ -33,6 +33,29 @@ ok() { echo " ✓ $1"; } skip() { echo " ⊘ $1 — skipped ($2)"; } fail() { echo " ✗ $1 — $2"; } +# A restored cache can hold an environment whose interpreter no longer exists: +# CI caches only this directory, and a venv's `bin/python` is a symlink (or a +# launcher shebang) into an interpreter outside it. uv in particular links to +# its own managed Python under ~/.local/share/uv, which a fresh runner lacks, +# so `bin/dbt` is present on disk yet fails with ENOENT when executed. The +# `.done` marker alone therefore proves nothing; a cached environment counts +# only if its dbt actually runs. +with_timeout() { + if has timeout; then timeout "$TIMEOUT" "$@"; else "$@"; fi +} +env_ok() { + [ -x "$1" ] && with_timeout "$1" --version >/dev/null 2>&1 +} +# Usage: cached_or_rebuild ; returns 0 when the cached +# environment is usable (caller returns), 1 when it must be rebuilt. +cached_or_rebuild() { + local dir="$ENVS_DIR/$1" + [ -f "$dir/.done" ] || return 1 + if env_ok "$2"; then ok "$1 (cached)"; return 0; fi + echo " ↻ $1 cache is stale (dbt does not run) — rebuilding..." + return 1 +} + # Find a real (non-shim) python3 for venv creation find_real_python() { # Try pyenv's actual python first @@ -55,7 +78,7 @@ echo "Using Python: $REAL_PYTHON ($($REAL_PYTHON --version 2>&1))" setup_venv() { local dir="$ENVS_DIR/venv" - if [ -f "$dir/.done" ]; then ok "venv (cached)"; return; fi + cached_or_rebuild venv "$dir/bin/dbt" && return rm -rf "$dir" echo " → Setting up venv..." "$REAL_PYTHON" -m venv "$dir" @@ -68,12 +91,14 @@ setup_venv() { setup_uv() { local dir="$ENVS_DIR/uv" if ! has uv; then skip "uv" "uv not installed"; return; fi - if [ -f "$dir/.done" ]; then ok "uv (cached)"; return; fi + cached_or_rebuild uv "$dir/.venv/bin/dbt" && return rm -rf "$dir" echo " → Setting up uv..." mkdir -p "$dir" - # uv project mode: create .venv in dir - uv venv "$dir/.venv" --quiet + # uv project mode: create .venv in dir. Pin the interpreter to the system + # Python so the venv survives a cache restore on a fresh runner; left to + # itself uv links a managed Python that lives outside the cached directory. + uv venv "$dir/.venv" --python "$REAL_PYTHON" --quiet uv pip install --quiet --python "$dir/.venv/bin/python" "$DBT_SPEC" touch "$dir/.done" ok "uv ($("$dir/.venv/bin/dbt" --version 2>&1 | grep -oE 'installed:\s+\S+' | head -1))" @@ -82,7 +107,7 @@ setup_uv() { setup_pipx() { local dir="$ENVS_DIR/pipx" if ! has pipx; then skip "pipx" "pipx not installed"; return; fi - if [ -f "$dir/.done" ]; then ok "pipx (cached)"; return; fi + cached_or_rebuild pipx "$dir/bin/dbt" && return rm -rf "$dir" echo " → Setting up pipx..." mkdir -p "$dir/bin" "$dir/venvs" @@ -100,7 +125,7 @@ setup_pipx() { setup_conda() { local dir="$ENVS_DIR/conda" if ! has conda; then skip "conda" "conda not installed"; return; fi - if [ -f "$dir/.done" ]; then ok "conda (cached)"; return; fi + cached_or_rebuild conda "$dir/bin/dbt" && return rm -rf "$dir" echo " → Setting up conda..." conda create -y -p "$dir" python=3.11 --quiet 2>/dev/null @@ -117,7 +142,7 @@ setup_conda() { setup_poetry() { local dir="$ENVS_DIR/poetry" if ! has poetry; then skip "poetry" "poetry not installed"; return; fi - if [ -f "$dir/.done" ]; then ok "poetry (cached)"; return; fi + cached_or_rebuild poetry "$dir/.venv/bin/dbt" && return rm -rf "$dir" echo " → Setting up poetry (in-project venv)..." mkdir -p "$dir" @@ -153,7 +178,7 @@ setup_pyenv_venv() { # Simulates a pyenv user who creates a venv with their pyenv-managed python local dir="$ENVS_DIR/pyenv-venv" if ! has pyenv; then skip "pyenv-venv" "pyenv not installed"; return; fi - if [ -f "$dir/.done" ]; then ok "pyenv-venv (cached)"; return; fi + cached_or_rebuild pyenv-venv "$dir/bin/dbt" && return rm -rf "$dir" echo " → Setting up pyenv + venv..." local pyenv_python From 3a4c9937f7b65ce2ecef4c5d9b6bf649b14531fe Mon Sep 17 00:00:00 2001 From: anandgupta42 <93243293+anandgupta42@users.noreply.github.com> Date: Mon, 7 Sep 2026 14:19:30 -0700 Subject: [PATCH 2/6] ci: run the dbt-tools E2E job on pull requests that touch packages/dbt-tools The job ran only on push to main, so a PR fixing its environment setup could not prove the fix before merging. The `dbt-tools` change filter already existed for exactly this; the job now honours it, keeping the 3-minute cost to PRs that change dbt-tools. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aff5eda95f..0022e97387 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -546,14 +546,17 @@ jobs: # altimate_change end # --------------------------------------------------------------------------- - # dbt-tools E2E — slow (~3 min), only on push to main. + # dbt-tools E2E — slow (~3 min): on push to main, and on pull requests that + # touch packages/dbt-tools (the `dbt-tools` change filter above). Without the + # PR path a fix to this job's own environment setup could only be proven + # after merging. # Tests dbt CLI fallbacks against real dbt versions (1.8, 1.10, 1.11) and # real Python environments (venv, uv, system). # --------------------------------------------------------------------------- dbt-tools-e2e: name: "dbt-tools E2E" needs: changes - if: github.event_name == 'push' + if: github.event_name == 'push' || needs.changes.outputs.dbt-tools == 'true' runs-on: ubuntu-latest timeout-minutes: 10 steps: From b443456e768c7e1a25b686e49cad59e988a02117 Mon Sep 17 00:00:00 2001 From: anandgupta42 <93243293+anandgupta42@users.noreply.github.com> Date: Mon, 7 Sep 2026 17:34:15 -0700 Subject: [PATCH 3/6] =?UTF-8?q?ci(e2e):=20review=20fixes=20=E2=80=94=20gti?= =?UTF-8?q?meout=20fallback,=20setup-python=20interpreter,=20read-only=20t?= =?UTF-8?q?oken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - setup-resolve.sh: `with_timeout` falls back to Homebrew's `gtimeout` on macOS, so the cached-env check is bounded there too (cubic). - setup-resolve.sh: `find_real_python` honours `DBT_E2E_PYTHON`; the workflow sets it to the interpreter actions/setup-python installed, so the scenario venvs no longer build on the runner image's /usr/bin/python3 while the workflow believes it chose 3.11 (CodeRabbit). - ci.yml: the E2E job now runs pull-request code, so it gets `permissions: contents: read` and `persist-credentials: false`, matching the tracker-leaks job (CodeRabbit). Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci.yml | 11 +++++++++++ packages/dbt-tools/test/e2e/setup-resolve.sh | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0022e97387..1ebe25faf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -559,14 +559,21 @@ jobs: if: github.event_name == 'push' || needs.changes.outputs.dbt-tools == 'true' runs-on: ubuntu-latest timeout-minutes: 10 + # Runs pull-request code (setup scripts, tests), so it gets the same + # read-only token scope and no persisted credentials as tracker-leaks. + permissions: + contents: read steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2 with: bun-version: "1.3.14" - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + id: python with: python-version: "3.11" @@ -598,6 +605,10 @@ jobs: - name: Set up Python env scenarios run: ./test/e2e/setup-resolve.sh venv uv system working-directory: packages/dbt-tools + env: + # Build the scenario venvs on the interpreter setup-python installed, + # not the image's /usr/bin/python3; setup-resolve.sh honours this. + DBT_E2E_PYTHON: ${{ steps.python.outputs.python-path }} - name: Run dbt-tools E2E tests run: bun run test:e2e diff --git a/packages/dbt-tools/test/e2e/setup-resolve.sh b/packages/dbt-tools/test/e2e/setup-resolve.sh index 1b1ad8c239..d00c8d288a 100755 --- a/packages/dbt-tools/test/e2e/setup-resolve.sh +++ b/packages/dbt-tools/test/e2e/setup-resolve.sh @@ -41,7 +41,11 @@ fail() { echo " ✗ $1 — $2"; } # `.done` marker alone therefore proves nothing; a cached environment counts # only if its dbt actually runs. with_timeout() { - if has timeout; then timeout "$TIMEOUT" "$@"; else "$@"; fi + # GNU timeout on Linux; Homebrew coreutils installs it as gtimeout on macOS. + # Without either, the check still runs, just unbounded. + if has timeout; then timeout "$TIMEOUT" "$@" + elif has gtimeout; then gtimeout "$TIMEOUT" "$@" + else "$@"; fi } env_ok() { [ -x "$1" ] && with_timeout "$1" --version >/dev/null 2>&1 @@ -58,6 +62,10 @@ cached_or_rebuild() { # Find a real (non-shim) python3 for venv creation find_real_python() { + # An explicit interpreter wins. CI sets DBT_E2E_PYTHON to the interpreter + # actions/setup-python installed, so the venvs build on the Python the + # workflow chose rather than whatever the runner image happens to ship. + if [ -n "${DBT_E2E_PYTHON:-}" ] && [ -x "$DBT_E2E_PYTHON" ]; then echo "$DBT_E2E_PYTHON"; return; fi # Try pyenv's actual python first if has pyenv; then local p From 6cec6f40ca6758f4392cae8c326d92f0eebd8590 Mon Sep 17 00:00:00 2001 From: "Ralph Sto. Domingo" Date: Fri, 9 Oct 2026 13:14:17 +0800 Subject: [PATCH 4/6] fix(ci): rebuild stale dbt version venvs; stop the versions suite skipping - setup-versions.sh rebuilds a cached venv whose dbt does not run - key both caches on the setup-python version - versions test fails on a missing requested version, and points dbt-cli at each venv - job timeout 20 min Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_0174HUYiaceApqHP7EKMNgnb --- .github/workflows/ci.yml | 14 ++++++++--- .../dbt-tools/test/e2e/dbt-versions.test.ts | 22 ++++++++++++++---- packages/dbt-tools/test/e2e/setup-versions.sh | 23 +++++++++++++++---- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cae4503835..7e8211ea38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -610,7 +610,9 @@ jobs: needs: changes if: github.event_name == 'push' || needs.changes.outputs.dbt-tools == 'true' runs-on: ubuntu-latest - timeout-minutes: 10 + # The multi-version suite runs dbt seed/build/show/compile for each version; + # on a cache miss the venvs are rebuilt first. + timeout-minutes: 20 # Runs pull-request code (setup scripts, tests), so it gets the same # read-only token scope and no persisted credentials as tracker-leaks. permissions: @@ -639,7 +641,11 @@ jobs: uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: packages/dbt-tools/test/.dbt-venvs - key: dbt-venvs-${{ runner.os }}-1.8-1.10-1.11 + # Both caches hold venvs linked to the setup-python interpreter, which + # is not cached. Keying on its exact version starts a fresh cache when + # the runner's 3.11 patch release moves, instead of restoring venvs + # that no longer run. + key: dbt-venvs-${{ runner.os }}-py${{ steps.python.outputs.python-version }}-1.8-1.10-1.11 - name: Cache Python env scenarios uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 @@ -648,11 +654,13 @@ jobs: # v2: the v1 cache held a uv venv linked to a uv-managed Python that # fresh runners lack (bin/dbt present, exec ENOENT). setup-resolve.sh # now validates cached envs and pins uv to the system interpreter. - key: dbt-resolve-envs-${{ runner.os }}-v2 + key: dbt-resolve-envs-${{ runner.os }}-py${{ steps.python.outputs.python-version }}-v2 - name: Set up dbt versions run: ./test/e2e/setup-versions.sh 1.8 1.10 1.11 working-directory: packages/dbt-tools + env: + DBT_E2E_PYTHON: ${{ steps.python.outputs.python-path }} - name: Set up Python env scenarios run: ./test/e2e/setup-resolve.sh venv uv system diff --git a/packages/dbt-tools/test/e2e/dbt-versions.test.ts b/packages/dbt-tools/test/e2e/dbt-versions.test.ts index 17ea69f3fe..c0363440da 100644 --- a/packages/dbt-tools/test/e2e/dbt-versions.test.ts +++ b/packages/dbt-tools/test/e2e/dbt-versions.test.ts @@ -12,7 +12,8 @@ * If no dbt is available at all, the entire suite is skipped. * * Environment variables: - * DBT_E2E_VERSIONS — comma-separated list of versions to test (e.g. "1.8,1.9") + * DBT_E2E_VERSIONS — comma-separated list of versions to test (e.g. "1.8,1.9"); + * a listed version without a working dbt fails the run * DBT_E2E_SKIP — set to "1" to skip e2e tests entirely */ @@ -29,6 +30,7 @@ import { tmpdir } from "os" const FIXTURE_DIR = resolve(import.meta.dir, "../fixture") const VENVS_DIR = resolve(import.meta.dir, "../.dbt-venvs") const SKIP = process.env.DBT_E2E_SKIP === "1" +const REQUESTED = process.env.DBT_E2E_VERSIONS?.split(",").map((v) => v.trim()) /** Timeout for dbt commands (seed + build can be slow on first run) */ const DBT_TIMEOUT = 120_000 @@ -51,13 +53,12 @@ interface DbtVersion { } function discoverVersions(): DbtVersion[] { - const filterVersions = process.env.DBT_E2E_VERSIONS?.split(",").map((v) => v.trim()) const versions: DbtVersion[] = [] // Check venvs if (existsSync(VENVS_DIR)) { for (const entry of readdirSync(VENVS_DIR)) { - if (filterVersions && !filterVersions.includes(entry)) continue + if (REQUESTED && !REQUESTED.includes(entry)) continue const dbtPath = join(VENVS_DIR, entry, "bin", "dbt") const pythonPath = join(VENVS_DIR, entry, "bin", "python") if (!existsSync(dbtPath)) continue @@ -98,6 +99,14 @@ if (!HAS_DBT && !SKIP) { ) } +// Asking for versions (CI does) makes a missing one a failure, not a skip: a +// stale venv cache once skipped every test below on every run without a sign. +describe.skipIf(SKIP || !REQUESTED)("requested dbt versions", () => { + test("every version in DBT_E2E_VERSIONS has a dbt that runs", () => { + expect(VERSIONS.map((v) => v.label).sort()).toEqual([...(REQUESTED ?? [])].sort()) + }) +}) + // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- @@ -205,10 +214,15 @@ describe.skipIf(!HAS_DBT)("altimate-dbt e2e", () => { describe(`dbt ${version.label} (${version.full})`, () => { let workDir: string - beforeAll(() => { + beforeAll(async () => { console.log(`\n→ Setting up dbt ${version.full} project...`) workDir = setupProject(version) console.log(` Project: ${workDir}`) + // dbt-cli.ts caches the dbt it resolves on first use. configure() + // clears that cache and points it at this version's venv; without it + // every version after the first runs the first version's dbt. + const { configure } = await import("../../src/dbt-cli") + configure({ pythonPath: version.pythonPath, projectRoot: workDir }) }, DBT_TIMEOUT * 2) afterAll(() => { diff --git a/packages/dbt-tools/test/e2e/setup-versions.sh b/packages/dbt-tools/test/e2e/setup-versions.sh index 34612ed0a2..d8cb6e3712 100755 --- a/packages/dbt-tools/test/e2e/setup-versions.sh +++ b/packages/dbt-tools/test/e2e/setup-versions.sh @@ -47,6 +47,17 @@ get_pip_flags() { esac } +# CI passes the interpreter actions/setup-python installed; locally the first +# python3 on PATH is used. +PYTHON="${DBT_E2E_PYTHON:-python3}" + +# Prints e.g. `core=1.8.7`, or nothing when the binary does not run. A restored +# cache can hold a venv whose bin/python links to an interpreter that no longer +# exists, so bin/dbt being on disk proves nothing. +installed_version() { + "$1" --version 2>&1 | grep -oE 'installed: [0-9.a-z]+' | head -1 | sed 's/installed: /core=/' || true +} + mkdir -p "$VENVS_DIR" for ver in "${VERSIONS[@]}"; do @@ -60,13 +71,17 @@ for ver in "${VERSIONS[@]}"; do fi if [ -f "$venv_dir/bin/dbt" ]; then - existing=$("$venv_dir/bin/dbt" --version 2>&1 | grep -oE 'installed: [0-9.a-z]+' | head -1 | sed 's/installed: /core=/' | head -1 || echo "unknown") - echo "✓ dbt $ver already installed ($existing) at $venv_dir" - continue + existing=$(installed_version "$venv_dir/bin/dbt") + if [ -n "$existing" ]; then + echo "✓ dbt $ver already installed ($existing) at $venv_dir" + continue + fi + echo "↻ dbt $ver cache is stale (dbt does not run) — rebuilding..." + rm -rf "$venv_dir" fi echo "→ Installing dbt $ver..." - python3 -m venv "$venv_dir" + "$PYTHON" -m venv "$venv_dir" "$venv_dir/bin/pip" install --quiet --upgrade pip # shellcheck disable=SC2086 "$venv_dir/bin/pip" install --quiet $extra_flags $install_spec From 591fdf728464d64ac818ce7d92cb0cd183321da4 Mon Sep 17 00:00:00 2001 From: "Ralph Sto. Domingo" Date: Fri, 9 Oct 2026 13:43:31 +0800 Subject: [PATCH 5/6] ci: keep the dbt-tools E2E job at 10 min The full run takes about 4 min in CI on a cache miss (tests 128 s), so the original limit fits. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_0174HUYiaceApqHP7EKMNgnb --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e8211ea38..e927811c37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -598,7 +598,7 @@ jobs: # altimate_change end # --------------------------------------------------------------------------- - # dbt-tools E2E — slow (~3 min): on push to main, and on pull requests that + # dbt-tools E2E — slow (~4 min): on push to main, and on pull requests that # touch packages/dbt-tools (the `dbt-tools` change filter above). Without the # PR path a fix to this job's own environment setup could only be proven # after merging. @@ -610,9 +610,7 @@ jobs: needs: changes if: github.event_name == 'push' || needs.changes.outputs.dbt-tools == 'true' runs-on: ubuntu-latest - # The multi-version suite runs dbt seed/build/show/compile for each version; - # on a cache miss the venvs are rebuilt first. - timeout-minutes: 20 + timeout-minutes: 10 # Runs pull-request code (setup scripts, tests), so it gets the same # read-only token scope and no persisted credentials as tracker-leaks. permissions: From 1381124cbb4154daa175d88ffb863ae883114c50 Mon Sep 17 00:00:00 2001 From: "Ralph Sto. Domingo" Date: Fri, 9 Oct 2026 13:46:13 +0800 Subject: [PATCH 6/6] fix(ci): trust a cached dbt venv only if it runs and reports its version - setup-versions.sh requires `dbt --version` to exit 0 and report the requested minor; otherwise it rebuilds - the versions guard compares the version each dbt reports, not dir names Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_0174HUYiaceApqHP7EKMNgnb --- packages/dbt-tools/test/e2e/dbt-versions.test.ts | 9 ++++++--- packages/dbt-tools/test/e2e/setup-versions.sh | 10 ++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/dbt-tools/test/e2e/dbt-versions.test.ts b/packages/dbt-tools/test/e2e/dbt-versions.test.ts index c0363440da..a90586f872 100644 --- a/packages/dbt-tools/test/e2e/dbt-versions.test.ts +++ b/packages/dbt-tools/test/e2e/dbt-versions.test.ts @@ -13,7 +13,8 @@ * * Environment variables: * DBT_E2E_VERSIONS — comma-separated list of versions to test (e.g. "1.8,1.9"); - * a listed version without a working dbt fails the run + * a listed version without a working dbt of that version + * fails the run * DBT_E2E_SKIP — set to "1" to skip e2e tests entirely */ @@ -102,8 +103,10 @@ if (!HAS_DBT && !SKIP) { // Asking for versions (CI does) makes a missing one a failure, not a skip: a // stale venv cache once skipped every test below on every run without a sign. describe.skipIf(SKIP || !REQUESTED)("requested dbt versions", () => { - test("every version in DBT_E2E_VERSIONS has a dbt that runs", () => { - expect(VERSIONS.map((v) => v.label).sort()).toEqual([...(REQUESTED ?? [])].sort()) + test("every version in DBT_E2E_VERSIONS has a dbt that runs and reports it", () => { + // Compare what each dbt reports (1.11.0-b3 → 1.11), not its directory name. + const reported = VERSIONS.map((v) => v.full.split(".").slice(0, 2).join(".")) + expect(reported.sort()).toEqual([...(REQUESTED ?? [])].sort()) }) }) diff --git a/packages/dbt-tools/test/e2e/setup-versions.sh b/packages/dbt-tools/test/e2e/setup-versions.sh index d8cb6e3712..b069ae7b33 100755 --- a/packages/dbt-tools/test/e2e/setup-versions.sh +++ b/packages/dbt-tools/test/e2e/setup-versions.sh @@ -51,11 +51,13 @@ get_pip_flags() { # python3 on PATH is used. PYTHON="${DBT_E2E_PYTHON:-python3}" -# Prints e.g. `core=1.8.7`, or nothing when the binary does not run. A restored +# Prints e.g. `core=1.8.7`, or nothing when `dbt --version` fails. A restored # cache can hold a venv whose bin/python links to an interpreter that no longer # exists, so bin/dbt being on disk proves nothing. installed_version() { - "$1" --version 2>&1 | grep -oE 'installed: [0-9.a-z]+' | head -1 | sed 's/installed: /core=/' || true + local out + out=$("$1" --version 2>&1) || return 0 + echo "$out" | grep -oE 'installed: [0-9.a-z]+' | head -1 | sed 's/installed: /core=/' || true } mkdir -p "$VENVS_DIR" @@ -72,11 +74,11 @@ for ver in "${VERSIONS[@]}"; do if [ -f "$venv_dir/bin/dbt" ]; then existing=$(installed_version "$venv_dir/bin/dbt") - if [ -n "$existing" ]; then + if [[ "$existing" == "core=$ver."* ]]; then echo "✓ dbt $ver already installed ($existing) at $venv_dir" continue fi - echo "↻ dbt $ver cache is stale (dbt does not run) — rebuilding..." + echo "↻ dbt $ver cache is stale (found ${existing:-no dbt that runs}) — rebuilding..." rm -rf "$venv_dir" fi