Skip to content

Commit a9da026

Browse files
committed
acc: make acceptance tests work in Databricks development environments
The acceptance harness blocks external network access during tests. In some Databricks-internal development environments, local tooling issues network calls that trip the sandbox and fail the suite even though CI passes. Ignore the local probes that are not test traffic and disable the background beacons so tests behave the same as on CI. Also assert the external toolchain (jq, uv, ruff) and provision python via uv up front, so a missing or stale tool fails fast with a clear message. Co-authored-by: Isaac
1 parent 179c38c commit a9da026

14 files changed

Lines changed: 318 additions & 7 deletions

File tree

.github/workflows/check.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ jobs:
3535
- name: Fail if go.mod/go.sum changed
3636
run: git diff --exit-code
3737

38+
- name: Install uv
39+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
40+
with:
41+
version: "0.8.9"
42+
3843
- name: Run Go lint checks (does not include formatting checks)
3944
run: go tool -modfile=tools/task/go.mod task lint
4045

@@ -44,11 +49,6 @@ jobs:
4449
version: "0.9.1"
4550
args: "format --check"
4651

47-
- name: Install uv
48-
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
49-
with:
50-
version: "0.8.9"
51-
5252
- name: "task fmt: Python and Go formatting"
5353
# Python formatting is already checked above, but this also checks Go and YAML formatting
5454
run: |

Taskfile.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ vars:
1212
# a separate static `**/testdata/**` glob, not this script.
1313
# Limitation: git grep only scans tracked files; new //go:embed directives in
1414
# untracked files are missed until the file is staged or committed.
15+
# Run via uv with a pinned interpreter floor so the helper works on hosts whose
16+
# default python3 is older than the 3.11 this repo's scripts target.
1517
EMBED_SOURCES:
16-
sh: 'python3 tools/list_embeds.py'
18+
sh: 'uv run -p ">=3.11" --no-project python tools/list_embeds.py'
1719

1820
# pydabs-* tasks live in python/Taskfile.yml so `task pydabs-foo` works when
1921
# run from python/. Flattened so they keep their `pydabs-` names at the root.
@@ -948,7 +950,7 @@ tasks:
948950
generates:
949951
- bundle/direct/dresources/apitypes.generated.yml
950952
cmds:
951-
- "sh -c 'python3 bundle/direct/tools/generate_apitypes.py .codegen/cli.json acceptance/bundle/refschema/out.fields.txt > bundle/direct/dresources/apitypes.generated.yml'"
953+
- "sh -c 'uv run -p \">=3.11\" --no-project python bundle/direct/tools/generate_apitypes.py .codegen/cli.json acceptance/bundle/refschema/out.fields.txt > bundle/direct/dresources/apitypes.generated.yml'"
952954

953955
generate-direct-resources:
954956
desc: Generate direct engine resources YAML

acceptance/acceptance_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,14 @@ func testAccept(t *testing.T, inprocessMode bool, singleTest string) int {
229229
os.Unsetenv(v) //nolint:usetesting // t.Setenv cannot unset
230230
}
231231

232+
// Verify external tool prerequisites before doing any work, so a stale
233+
// toolchain fails fast with an actionable message instead of producing
234+
// confusing diffs deep into the run.
235+
internal.RequireModernJq(t)
236+
internal.RequireModernUv(t)
237+
internal.RequireModernRuff(t)
238+
internal.EnsureModernPython(t)
239+
232240
buildDir := getBuildDir(t, cwd, runtime.GOOS, runtime.GOARCH)
233241

234242
// Set up terraform for tests. Skip on DBR - tests with RunsOnDbr only use direct deployment.
@@ -759,6 +767,21 @@ func runTest(t *testing.T,
759767
// into compared output. Tests can override this via [Env] in test.toml.
760768
cmd.Env = append(cmd.Env, "DATABRICKS_CLI_DISABLE_UPDATE_CHECK=true")
761769

770+
// Neutralize Databricks-internal development-environment interference so
771+
// acceptance tests behave the same as on CI (which has none of this). Two
772+
// sources both reach the blocking proxy on every git invocation:
773+
//
774+
// 1. A command-timing shim that wraps git (ahead of the real binary on
775+
// PATH) and POSTs per-command metrics over the network.
776+
// COMMAND_TIMER_DISABLE=1 makes it pass through without the beacon.
777+
// 2. A managed global git config installs a core.hooksPath whose hooks
778+
// (secret scanning, etc.) also beacon metrics. Ignoring the global and
779+
// system git config disables those hooks and keeps tests hermetic; tests
780+
// configure the repos they create via git-repo-init locally.
781+
cmd.Env = append(cmd.Env, "COMMAND_TIMER_DISABLE=1")
782+
cmd.Env = append(cmd.Env, "GIT_CONFIG_GLOBAL="+os.DevNull)
783+
cmd.Env = append(cmd.Env, "GIT_CONFIG_SYSTEM="+os.DevNull)
784+
762785
for _, kv := range testEnv {
763786
key, value, _ := strings.Cut(kv, "=")
764787
// Only add replacement by default if value is part of EnvMatrix with more than 1 option and length is 4 or more chars

acceptance/internal/jq.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package internal
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"strings"
7+
"testing"
8+
)
9+
10+
// RequireModernJq fails the run if jq is missing or older than 1.7. Acceptance
11+
// scripts use jq 1.7 features (the pick/1 builtin and the `.foo.[]` iteration
12+
// syntax); an older jq compiles them as errors and produces spurious diffs
13+
// across many tests rather than one clear failure.
14+
func RequireModernJq(t *testing.T) {
15+
out, err := exec.Command("jq", "--version").Output()
16+
if err != nil {
17+
t.Fatalf("jq not found on PATH (acceptance tests require jq >= 1.7): %v", err)
18+
}
19+
version := strings.TrimSpace(string(out))
20+
if !jqVersionOK(version) {
21+
t.Fatalf("acceptance tests require jq >= 1.7 (found %q); install a newer jq", version)
22+
}
23+
}
24+
25+
// jqVersionOK reports whether `jq --version` output (e.g. "jq-1.7.1") is >= 1.7.
26+
func jqVersionOK(version string) bool {
27+
var major, minor int
28+
if _, err := fmt.Sscanf(version, "jq-%d.%d", &major, &minor); err != nil {
29+
return false
30+
}
31+
return major > 1 || (major == 1 && minor >= 7)
32+
}

acceptance/internal/jq_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package internal
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestJqVersionOK(t *testing.T) {
10+
assert.True(t, jqVersionOK("jq-1.7"))
11+
assert.True(t, jqVersionOK("jq-1.7.1"))
12+
assert.True(t, jqVersionOK("jq-1.8.1"))
13+
assert.True(t, jqVersionOK("jq-2.0"))
14+
assert.False(t, jqVersionOK("jq-1.6"))
15+
assert.False(t, jqVersionOK("jq version 1.7"))
16+
assert.False(t, jqVersionOK(""))
17+
}

acceptance/internal/python.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package internal
2+
3+
import (
4+
"os"
5+
"os/exec"
6+
"path/filepath"
7+
"runtime"
8+
"strings"
9+
"testing"
10+
)
11+
12+
// EnsureModernPython makes `python3` on PATH resolve to a Python >= 3.11, the
13+
// version this repo's scripts target. Acceptance scripts invoke `python3`
14+
// directly and some import stdlib modules added in 3.11 (e.g. tomllib in
15+
// acceptance/bundle/resources/permissions/analyze_requests.py), but a host's
16+
// default python3 may be older. uv (already required for building the
17+
// databricks-bundles wheel) discovers or provisions a suitable interpreter; we
18+
// symlink it as python3/python into a temp dir prepended to PATH so every
19+
// script and build step resolves it. Fails hard if uv is missing or has no
20+
// suitable Python.
21+
func EnsureModernPython(t *testing.T) {
22+
// Windows runners already ship a python3 >= 3.11, and os.Symlink needs extra
23+
// privileges there, so don't provision: use the interpreter already on PATH.
24+
if runtime.GOOS == "windows" {
25+
return
26+
}
27+
28+
out, err := exec.Command("uv", "python", "find", ">=3.11").Output()
29+
if err != nil {
30+
t.Fatalf("uv could not find python >= 3.11: %v", err)
31+
}
32+
python := strings.TrimSpace(string(out))
33+
34+
binDir := t.TempDir()
35+
for _, link := range []string{"python3", "python"} {
36+
if err := os.Symlink(python, filepath.Join(binDir, link)); err != nil {
37+
t.Fatalf("failed to symlink %s as %s: %v", python, link, err)
38+
}
39+
}
40+
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH")) //nolint:forbidigo // acceptance test harness; no ctx for libs/env
41+
t.Logf("acceptance tests: using %s (via uv) as python3", python)
42+
}

acceptance/internal/python_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package internal
2+
3+
import (
4+
"os/exec"
5+
"strings"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestEnsureModernPython(t *testing.T) {
13+
if _, err := exec.LookPath("uv"); err != nil {
14+
t.Skip("uv not installed")
15+
}
16+
17+
EnsureModernPython(t)
18+
19+
// After setup, the python3 resolved from PATH must satisfy the floor.
20+
out, err := exec.Command("python3", "-c", "import sys; print(sys.version_info >= (3, 11))").Output()
21+
require.NoError(t, err)
22+
assert.Equal(t, "True", strings.TrimSpace(string(out)))
23+
}

acceptance/internal/rejecting_proxy.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"net/http"
88
"strings"
99
"testing"
10+
11+
"github.com/databricks/cli/libs/testserver"
1012
)
1113

1214
// StartRejectingProxy starts an HTTP proxy server bound to a loopback port and
@@ -93,6 +95,12 @@ func handleBlockedConnection(t *testing.T, conn net.Conn, hint string) {
9395
if isLoopback || isReserved {
9496
// Expected unreachable fixture or local test server — log only, don't fail.
9597
t.Logf("blocking proxy: blocked loopback/reserved host: %s", detail)
98+
} else if testserver.IsLocalhostProbe(req) {
99+
// Some Databricks-internal development environments run a port watcher
100+
// that auto-forwards every new localhost listener and probes it with
101+
// `HEAD / Host: localhost`. This is not the CLI-under-test reaching the
102+
// internet, so log it instead of failing the test.
103+
t.Logf("blocking proxy: ignored localhost port-classification probe: %s", detail)
96104
} else {
97105
t.Errorf("internet access blocked by proxy: %s%s", detail, hint)
98106
}

acceptance/internal/ruff.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package internal
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"strings"
7+
"testing"
8+
)
9+
10+
// RequireModernRuff fails the run if ruff is missing or older than 0.9.1, the
11+
// version pinned across the repo (python/pyproject.toml, Taskfile.yml). The
12+
// pydabs check-formatting acceptance test runs `ruff format` and its golden
13+
// output assumes that formatter behavior.
14+
func RequireModernRuff(t *testing.T) {
15+
out, err := exec.Command("ruff", "--version").Output()
16+
if err != nil {
17+
t.Fatalf("ruff not found on PATH (acceptance tests require ruff >= 0.9.1): %v", err)
18+
}
19+
version := strings.TrimSpace(string(out))
20+
if !ruffVersionOK(version) {
21+
t.Fatalf("acceptance tests require ruff >= 0.9.1 (found %q); install a newer ruff", version)
22+
}
23+
}
24+
25+
// ruffVersionOK reports whether `ruff --version` output (e.g. "ruff 0.9.1") is >= 0.9.1.
26+
func ruffVersionOK(version string) bool {
27+
var major, minor, patch int
28+
if _, err := fmt.Sscanf(version, "ruff %d.%d.%d", &major, &minor, &patch); err != nil {
29+
return false
30+
}
31+
if major != 0 {
32+
return major > 0
33+
}
34+
if minor != 9 {
35+
return minor > 9
36+
}
37+
return patch >= 1
38+
}

acceptance/internal/ruff_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package internal
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestRuffVersionOK(t *testing.T) {
10+
assert.True(t, ruffVersionOK("ruff 0.9.1"))
11+
assert.True(t, ruffVersionOK("ruff 0.9.2"))
12+
assert.True(t, ruffVersionOK("ruff 0.11.0"))
13+
assert.True(t, ruffVersionOK("ruff 1.0.0"))
14+
assert.False(t, ruffVersionOK("ruff 0.9.0"))
15+
assert.False(t, ruffVersionOK("ruff 0.8.5"))
16+
assert.False(t, ruffVersionOK(""))
17+
}

0 commit comments

Comments
 (0)