Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions acceptance/acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,17 +191,22 @@ func hasRunFilter() bool {
// requirePrerequisites verifies external tool prerequisites before doing any
// work, so a stale toolchain fails fast with an actionable message instead of
// producing confusing diffs deep into the run.
func requirePrerequisites(t *testing.T) {
// Scripts use jq 1.7 features (the pick/1 builtin and the `.foo.[]` iteration syntax).
internal.RequireJQ(t, "1.7")
// uv builds the databricks-bundles wheel and provides the test interpreter
// via `uv python find`, which landed in the 0.3 line.
internal.RequireUV(t, "0.4")
// ruff 0.9.1 is pinned across the repo (python/pyproject.toml, Taskfile.yml);
// the check-formatting test's golden output assumes its formatter behavior.
internal.RequireRuff(t, "0.9.1")
// Acceptance scripts import the stdlib tomllib module, added in Python 3.11.
internal.EnsurePython(t, "3.11")
//
// It reports whether all checks passed; a failure surfaces as
// TestAccept/prerequisites rather than a bare TestAccept.
func requirePrerequisites(t *testing.T) bool {
return t.Run("prerequisites", func(t *testing.T) {
// Scripts use jq 1.7 features (the pick/1 builtin and the `.foo.[]` iteration syntax).
internal.RequireJQ(t, "1.7")
// uv builds the databricks-bundles wheel and provides the test interpreter
// via `uv python find`, which landed in the 0.3 line.
internal.RequireUV(t, "0.4")
// ruff 0.9.1 is pinned across the repo (python/pyproject.toml, Taskfile.yml);
// the check-formatting test's golden output assumes its formatter behavior.
internal.RequireRuff(t, "0.9.1")
// Acceptance scripts import the stdlib tomllib module, added in Python 3.11.
internal.RequirePython(t, "3.11")
})
}

func testAccept(t *testing.T, inprocessMode bool, singleTest string) int {
Expand Down Expand Up @@ -245,7 +250,14 @@ func testAccept(t *testing.T, inprocessMode bool, singleTest string) int {
os.Unsetenv(v) //nolint:usetesting // t.Setenv cannot unset
}

requirePrerequisites(t)
if !requirePrerequisites(t) {
// Don't run the suite against a stale toolchain; the failed subtest
// has already marked the parent test as failed.
return 0
}
// Run after the version check passed; must use the top-level t so the PATH
// change survives for the rest of the run.
internal.ConfigurePython(t, "3.11")

buildDir := getBuildDir(t, cwd, runtime.GOOS, runtime.GOARCH)

Expand Down
35 changes: 27 additions & 8 deletions acceptance/internal/python.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@ import (
"golang.org/x/mod/semver"
)

// EnsurePython makes `python3` on PATH resolve to a Python >= minVersion (e.g.
// "3.11"). Acceptance scripts invoke `python3` directly and some import stdlib
// modules added in newer versions, but a host's default python3 may be older.
// On non-Windows hosts uv (see RequireUV) selects a compatible interpreter,
// which we symlink as python3/python into a temp dir prepended to PATH. On
// Windows os.Symlink needs extra privileges, so we instead require that the
// python3 already on PATH satisfies the floor.
func EnsurePython(t *testing.T, minVersion string) {
// RequirePython fails the test if no Python >= minVersion (e.g. "3.11") is
// available for the acceptance suite. It only verifies; ConfigurePython performs
// the PATH setup. On Windows os.Symlink needs extra privileges, so we require the
// python3 already on PATH to satisfy the floor; elsewhere uv must be able to find
// a compatible interpreter.
func RequirePython(t *testing.T, minVersion string) {
if runtime.GOOS == "windows" {
out, err := exec.Command("python3", "--version").Output()
if err != nil {
Expand All @@ -31,6 +29,27 @@ func EnsurePython(t *testing.T, minVersion string) {
return
}

if _, err := exec.Command("uv", "python", "find", ">="+minVersion).Output(); err != nil {
t.Fatalf("uv could not find python >= %s: %v", minVersion, err)
}
}

// ConfigurePython makes `python3` on PATH resolve to a Python >= minVersion for
// the rest of the run. Acceptance scripts invoke `python3` directly and some
// import stdlib modules added in newer versions, but a host's default python3
// may be older. On non-Windows hosts uv (see RequireUV) selects a compatible
// interpreter, which we symlink as python3/python into a temp dir prepended to
// PATH. On Windows we rely on the python3 already on PATH, which RequirePython
// has verified satisfies the floor.
//
// It must run on the top-level test's t: the t.Setenv and t.TempDir below are
// undone when that test returns, so calling it from a subtest would revert the
// PATH change before the rest of the suite runs.
func ConfigurePython(t *testing.T, minVersion string) {
if runtime.GOOS == "windows" {
return
}

out, err := exec.Command("uv", "python", "find", ">="+minVersion).Output()
if err != nil {
t.Fatalf("uv could not find python >= %s: %v", minVersion, err)
Expand Down
5 changes: 3 additions & 2 deletions acceptance/internal/python_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ func TestPythonVersionOK(t *testing.T) {
assert.False(t, pythonVersionOK("garbage", "3.11"))
}

func TestEnsurePython(t *testing.T) {
func TestConfigurePython(t *testing.T) {
if _, err := exec.LookPath("uv"); err != nil {
t.Skip("uv not installed")
}

EnsurePython(t, "3.11")
RequirePython(t, "3.11")
ConfigurePython(t, "3.11")

// After setup, the python3 resolved from PATH must satisfy the floor.
out, err := exec.Command("python3", "-c", "import sys; print(sys.version_info >= (3, 11))").Output()
Expand Down
Loading