-
Notifications
You must be signed in to change notification settings - Fork 209
integration: add e2e tests for environments setup-local #6155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
dcd31eb
9d9c0ac
a3549c1
c4d0836
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| package environments_test | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "os" | ||
| "path/filepath" | ||
| "runtime" | ||
| "testing" | ||
|
|
||
| "github.com/databricks/cli/integration/internal/acc" | ||
| "github.com/databricks/cli/internal/testcli" | ||
| "github.com/databricks/cli/libs/localenv" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| // The serverless target needs no running compute: --serverless-version is used | ||
| // verbatim, so these tests resolve against the real Jobs/Clusters-free path and | ||
| // fetch from the public databricks/environments repo. v5 is a published LTS | ||
| // target (see the repo's python/serverless/ tree). | ||
| const testServerlessVersion = "5" | ||
|
|
||
| // TestSetupLocalServerlessProvision drives the full non-dry-run pipeline against | ||
| // the real published constraints: resolve -> fetch -> uv sync -> validate. It | ||
| // asserts a real .venv is created and the --output json contract carries the | ||
| // resolved versions. This is the one integration test that exercises a real uv | ||
| // provision end to end; the acceptance suite covers everything else via --dry-run. | ||
| func TestSetupLocalServerlessProvision(t *testing.T) { | ||
| ctx, _ := acc.WorkspaceTest(t) | ||
|
|
||
| // setup-local operates on the current working directory; run in a fresh | ||
| // greenfield project (no pre-existing pyproject.toml). | ||
| dir := t.TempDir() | ||
| t.Chdir(dir) | ||
|
|
||
| // Let uv bootstrap itself if the runner's PATH lacks it; CI installs uv, but | ||
| // this keeps the test robust on a developer machine that opted in. | ||
| t.Setenv(localenv.EnvAutoInstallUv, "1") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This installs uv on a developer's machine as a side effect, and the comment has it backwards: the test sets the opt-in itself, so nobody opted in. On a machine without uv, Since CI already installs uv ( if _, err := exec.LookPath("uv"); err != nil {
t.Skipf("uv not found on PATH (%v)", err)
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 9d9c0ac — replaced with an |
||
|
|
||
| stdout, _ := testcli.RequireSuccessfulRun(t, ctx, | ||
| "environments", "setup-local", | ||
| "--serverless-version", testServerlessVersion, | ||
| "--output", "json", | ||
| ) | ||
|
|
||
| var res localenv.Result | ||
| require.NoError(t, json.Unmarshal(stdout.Bytes(), &res)) | ||
|
|
||
| assert.True(t, res.OK, "expected ok=true, got result: %s", stdout.String()) | ||
| assert.False(t, res.DryRun) | ||
| assert.Equal(t, "environments setup-local", res.Command) | ||
| assert.Equal(t, "default", res.Mode) | ||
|
|
||
| require.NotNil(t, res.Compute) | ||
| assert.Equal(t, "serverless", res.Compute.Source) | ||
| assert.Equal(t, "serverless/serverless-v"+testServerlessVersion, res.Compute.EnvKey) | ||
|
|
||
| require.NotNil(t, res.Resolved) | ||
| assert.NotEmpty(t, res.Resolved.PythonVersion, "resolved python version should be reported") | ||
| // The default mode installs databricks-connect, so the resolved pin is present. | ||
| assert.NotEmpty(t, res.Resolved.DBConnectVersion) | ||
| // The artifact came from a successful fetch. The cache is shared (UserCacheDir), | ||
| // so a prior run may have seeded it; accept either source rather than assuming | ||
| // a cold cache and flaking on re-runs. | ||
| assert.Contains(t, []string{"network", "cache"}, res.Resolved.ArtifactSource) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This assertion is tautological and can never fail. The reasoning in the comment is sound (avoid flaking on a warm shared cache) — the problem is the resulting assertion reads like coverage but checks nothing. Either isolate the cache so
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 9d9c0ac — deleted. |
||
|
|
||
| // Every phase must have reached ok, including the real provision and validate. | ||
| for _, ph := range res.Phases { | ||
| assert.Equalf(t, localenv.StatusOK, ph.Status, "phase %s not ok", ph.Phase) | ||
| } | ||
|
|
||
| // A real interpreter and lockfile were written into the project. testcli runs | ||
| // in-process, so runtime.GOOS is the runner's OS and picks the right layout. | ||
| assert.FileExists(t, venvPython(dir)) | ||
| assert.FileExists(t, filepath.Join(dir, "pyproject.toml")) | ||
| assert.FileExists(t, filepath.Join(dir, "uv.lock")) | ||
| } | ||
|
|
||
| // TestSetupLocalDryRunWritesNothing verifies the --dry-run contract against the | ||
| // real repo: the plan resolves and fetches, reports ok, but writes no files. | ||
| func TestSetupLocalDryRunWritesNothing(t *testing.T) { | ||
| ctx, _ := acc.WorkspaceTest(t) | ||
|
|
||
| dir := t.TempDir() | ||
| t.Chdir(dir) | ||
|
|
||
| stdout, _ := testcli.RequireSuccessfulRun(t, ctx, | ||
| "environments", "setup-local", | ||
| "--serverless-version", testServerlessVersion, | ||
| "--dry-run", | ||
| "--output", "json", | ||
| ) | ||
|
|
||
| var res localenv.Result | ||
| require.NoError(t, json.Unmarshal(stdout.Bytes(), &res)) | ||
| assert.True(t, res.OK) | ||
| assert.True(t, res.DryRun) | ||
|
|
||
| // --dry-run must not touch disk. | ||
| entries, err := os.ReadDir(dir) | ||
| require.NoError(t, err) | ||
| assert.Empty(t, entries, "dry-run wrote files: %v", entries) | ||
| } | ||
|
|
||
| // TestSetupLocalUnpublishedVersion verifies the fetch-phase error contract: an | ||
| // unpublished serverless version resolves fine but has no artifact, so the run | ||
| // fails with E_ENV_UNSUPPORTED and a non-zero exit (surfaced as a run error). | ||
| func TestSetupLocalUnpublishedVersion(t *testing.T) { | ||
| ctx, _ := acc.WorkspaceTest(t) | ||
|
|
||
| dir := t.TempDir() | ||
| t.Chdir(dir) | ||
|
|
||
| // A version far above anything published; resolution succeeds, fetch 404s. | ||
| stdout, _, runErr := testcli.RequireErrorRun(t, ctx, | ||
| "environments", "setup-local", | ||
| "--serverless-version", "9999", | ||
| "--dry-run", | ||
| "--output", "json", | ||
| ) | ||
| require.Error(t, runErr) | ||
|
|
||
| var res localenv.Result | ||
| require.NoError(t, json.Unmarshal(stdout.Bytes(), &res)) | ||
| assert.False(t, res.OK) | ||
| require.NotNil(t, res.Error) | ||
| assert.Equal(t, localenv.ErrEnvUnsupported, res.Error.Code) | ||
| assert.Equal(t, localenv.PhaseFetch, res.Error.FailurePhase) | ||
|
|
||
| // Even a failed fetch must not have provisioned anything on a dry run. | ||
| assert.NoFileExists(t, filepath.Join(dir, ".venv", "bin", "python")) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This hardcodes the Unix layout, defeating the Since this is a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 9d9c0ac — replaced with the |
||
| } | ||
|
|
||
| // venvPython returns the path to the created virtualenv's interpreter, accounting | ||
| // for the Windows (Scripts/python.exe) vs Unix (bin/python) layout. | ||
| func venvPython(dir string) string { | ||
| if runtime.GOOS == "windows" { | ||
| return filepath.Join(dir, ".venv", "Scripts", "python.exe") | ||
| } | ||
| return filepath.Join(dir, ".venv", "bin", "python") | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not this file, but related: OWNERS routes this file to
team:platformrather thanteam:ide./integration/→team:platform(.github/OWNERS:65) andfindOwnersis last-match-wins (.github/scripts/owners.js:97-106), so this file — squarely localenv territory — won't route to the team that owns/libs/localenv/,/cmd/environments/, and/acceptance/localenv/(added in #6187).Worth adding, and it must go below line 65 to win:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 9d9c0ac — added
/integration/cmd/environments/ team:idebelow the/integration/rule so it wins under last-match-wins. Verified withfindOwners: this file now resolves torugpanov rclarey anton-107 misha-db, whileintegration/cmd/jobs/still resolves toteam:platform.