From decd3fbc3f1be06dc2d0be2c2277edd11444c2de Mon Sep 17 00:00:00 2001 From: twsl <45483159+twsl@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:10:18 +0000 Subject: [PATCH 1/6] Update README for devcontainer setup and installation instructions --- README.md | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6a7ca464d..78c79bf4e 100644 --- a/README.md +++ b/README.md @@ -11,32 +11,28 @@ This repository contains the source code for Databricks extensions for VSCode. Currently, we have the following packages: -- [databricks-vscode](https://github.com/databricks/databricks-vscode/tree/main/packages/databricks-vscode) - The VSCode extension for Databricks published to the VSCode marketplace. -- [databricks-vscode-types](https://github.com/databricks/databricks-vscode/tree/main/packages/databricks-vscode-types) - Type definition of the public API of the VSCode extension. +- [databricks-vscode](https://github.com/databricks/databricks-vscode/tree/main/packages/databricks-vscode) + The VSCode extension for Databricks published to the VSCode marketplace. +- [databricks-vscode-types](https://github.com/databricks/databricks-vscode/tree/main/packages/databricks-vscode-types) + Type definition of the public API of the VSCode extension. ### Getting Started -Prepare yarn: +Start the [devcontainer](.devcontainer/devcontainer.json) or use the system of your choice with Node.js 22+ installed and prepare yarn: -``` +```sh npm install -g yarn@2 yarn install -``` - -Prepare Databricks JavaScript SDK: - -``` -yarn run install:sdk +yarn run build ``` Prepare Databricks CLI: -``` +```sh yarn workspace databricks run package:cli:fetch ``` +Then open the [code workspace](https://code.visualstudio.com/docs/editing/workspaces/workspaces). After that you are ready to build and test the `databricks-vscode` extension. ### Found an issue? From 976c97e335563597c9b4cfa2603bfd960530c7bc Mon Sep 17 00:00:00 2001 From: twsl <45483159+twsl@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:11:09 +0000 Subject: [PATCH 2/6] Fix deprecated alias for TypeScript auto detection setting in VSCode configuration --- .vscode/settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index edc206adb..116989f33 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,10 +9,10 @@ "dist": true // set this to false to include "dist" folder in search results }, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off", + "ts.tsc.autoDetect": "off", "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" } -} +} \ No newline at end of file From d50f8573407b78b6dae2321b91dd0de4d639e3de Mon Sep 17 00:00:00 2001 From: twsl <45483159+twsl@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:12:49 +0000 Subject: [PATCH 3/6] Enhance devcontainer configuration and update VSCode extension recommendations --- .devcontainer/devcontainer.json | 18 ++++++++++++++---- .../databricks-vscode/.vscode/extensions.json | 8 ++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 67f8f98ab..440b1900d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,22 +5,27 @@ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", "containerEnv": { - "COREPACK_ENABLE_DOWNLOAD_PROMPT": "0" + "COREPACK_ENABLE_DOWNLOAD_PROMPT": "0", + "CLI_ARCH": "linux_amd64" // Set the default CLI_ARCH to linux_amd64, for cli download script }, // Features to add to the dev container. More info: https://containers.dev/features. "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/git:1": {}, "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers/features/python:1": { + "version": "3.12" + }, + "ghcr.io/devcontainers/features/azure-cli:1": {}, "ghcr.io/devcontainers-extra/features/act:1": {}, "ghcr.io/devcontainers-extra/features/actionlint:1": {} }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "sudo corepack enable yarn && sudo corepack install && yarn install", + "postCreateCommand": "sudo corepack enable yarn && sudo corepack install", // Use 'postStartCommand' to run commands after the container is started. - "postStartCommand": "yarn -v", + "postStartCommand": "yarn install && yarn run build && yarn workspace databricks run package:cli:fetch", // Configure tool-specific properties. "customizations": { "vscode": { @@ -41,12 +46,17 @@ } }, "extensions": [ + "ms-python.python", + "ms-toolsai.jupyter", "dbaeumer.vscode-eslint", "connor4312.esbuild-problem-matchers", "ms-vscode.extension-test-runner", "amodio.tsl-problem-matcher", "esbenp.prettier-vscode", - "Tobermory.es6-string-html" + "Tobermory.es6-string-html", + "github.vscode-github-actions", + "redhat.vscode-xml", + "redhat.vscode-yaml" ] } }, diff --git a/packages/databricks-vscode/.vscode/extensions.json b/packages/databricks-vscode/.vscode/extensions.json index 503568fcb..39910e639 100644 --- a/packages/databricks-vscode/.vscode/extensions.json +++ b/packages/databricks-vscode/.vscode/extensions.json @@ -2,9 +2,13 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ + "ms-python.python", + "ms-toolsai.jupyter", "dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher", "esbenp.prettier-vscode", - "Tobermory.es6-string-html" + "Tobermory.es6-string-html", + "redhat.vscode-xml", + "redhat.vscode-yaml" ] -} +} \ No newline at end of file From 8413e7dcecb2057cd73d7b402edced0115e933d9 Mon Sep 17 00:00:00 2001 From: twsl <45483159+twsl@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:35:45 +0000 Subject: [PATCH 4/6] Support global Python environments --- .../src/language/EnvironmentCommands.ts | 94 ++++++++++++++++++- .../EnvironmentDependenciesVerifier.ts | 26 ++++- 2 files changed, 114 insertions(+), 6 deletions(-) diff --git a/packages/databricks-vscode/src/language/EnvironmentCommands.ts b/packages/databricks-vscode/src/language/EnvironmentCommands.ts index 5fc8e9cf5..5059182e9 100644 --- a/packages/databricks-vscode/src/language/EnvironmentCommands.ts +++ b/packages/databricks-vscode/src/language/EnvironmentCommands.ts @@ -80,7 +80,22 @@ export class EnvironmentCommands { if (environments.length > 0) { await this.showEnvironmentsQuickPick(environments); } else { - await this.pythonExtension.createPythonEnvironment(); + const createNewLabel = "$(add) Create new environment"; + const useExistingLabel = "$(globe) Use existing Python"; + const selectedPick = await window.showQuickPick( + [ + {label: createNewLabel, alwaysShow: true}, + {label: useExistingLabel, alwaysShow: true}, + ], + {title: "Select Python Environment"} + ); + if (selectedPick) { + if (selectedPick.label === createNewLabel) { + await this.pythonExtension.createPythonEnvironment(); + } else if (selectedPick.label === useExistingLabel) { + await this.showPythonInterpreterQuickPick(); + } + } } } @@ -93,10 +108,12 @@ export class EnvironmentCommands { }) ); const createNewLabel = "$(add) Create new environment"; + const useExistingLabel = "$(globe) Use existing Python"; const usePythonExtensionLabel = "$(gear) Use Python Extension to setup environments"; const staticPicks: QuickPickItem[] = [ {label: createNewLabel, alwaysShow: true}, + {label: useExistingLabel, alwaysShow: true}, {label: usePythonExtensionLabel, alwaysShow: true}, ]; const selectedPick = await window.showQuickPick( @@ -106,6 +123,8 @@ export class EnvironmentCommands { if (selectedPick) { if (selectedPick.label === createNewLabel) { await this.pythonExtension.createPythonEnvironment(); + } else if (selectedPick.label === useExistingLabel) { + await this.showPythonInterpreterQuickPick(); } else if (selectedPick.label === usePythonExtensionLabel) { await this.pythonExtension.selectPythonInterpreter(); } else if (selectedPick.path) { @@ -116,6 +135,79 @@ export class EnvironmentCommands { } } + private async showPythonInterpreterQuickPick() { + const environments = + await this.pythonExtension.getAllKnownEnvironments(); + if (environments.length === 0) { + window.showInformationMessage( + "No Python interpreters found. Install Python or create a virtual environment." + ); + return; + } + + type EnvPickItem = QuickPickItem & { + path: string; + isGlobal: boolean; + }; + const items: EnvPickItem[] = environments.map((env) => { + const isGlobal = !env.environment; + return { + label: environmentName(env), + description: isGlobal ? "Global" : env.environment?.type, + detail: env.path, + path: env.path, + isGlobal, + }; + }); + + const activeEnvPath = + this.pythonExtension.api.environments.getActiveEnvironmentPath(); + + const quickPick = window.createQuickPick(); + quickPick.title = "Select Python Interpreter"; + quickPick.items = items; + quickPick.canSelectMany = false; + + if (activeEnvPath) { + const currentItem = items.find( + (item) => item.path === activeEnvPath.path + ); + if (currentItem) { + quickPick.activeItems = [currentItem]; + } + } + + quickPick.show(); + + return new Promise((resolve) => { + quickPick.onDidAccept(async () => { + const selected = quickPick.selectedItems[0]; + quickPick.dispose(); + if (selected) { + if (selected.isGlobal) { + const confirm = await window.showWarningMessage( + "You selected a global Python interpreter. Installing packages like databricks-connect into a global Python may affect other applications. Consider using a virtual environment instead.", + {modal: true}, + "Use Global Python" + ); + if (confirm !== "Use Global Python") { + resolve(); + return; + } + } + await this.pythonExtension.api.environments.updateActiveEnvironmentPath( + selected.path + ); + } + resolve(); + }); + quickPick.onDidHide(() => { + quickPick.dispose(); + resolve(); + }); + }); + } + async reinstallDBConnect(cluster?: Cluster) { const state = await this.featureManager.isEnabled( "environment.dependencies" diff --git a/packages/databricks-vscode/src/language/EnvironmentDependenciesVerifier.ts b/packages/databricks-vscode/src/language/EnvironmentDependenciesVerifier.ts index 71d6b26eb..d6c6d6fb3 100644 --- a/packages/databricks-vscode/src/language/EnvironmentDependenciesVerifier.ts +++ b/packages/databricks-vscode/src/language/EnvironmentDependenciesVerifier.ts @@ -156,19 +156,30 @@ export class EnvironmentDependenciesVerifier extends MultiStepAccessVerifier { return this.acceptStep("checkWorkspaceHasUc"); } + private isGlobalInterpreter(env?: ResolvedEnvironment): boolean { + return ( + !!env && !env.environment && !!env.version && !!env.executable?.uri + ); + } + private matchEnvironmentVersion( env: ResolvedEnvironment | undefined, major: number, minor: number ): boolean { - if (!env || !env.version || !env.environment) { + if ( + !env || + !env.version || + (!env.environment && !this.isGlobalInterpreter(env)) + ) { return false; } return env.version.major === major && env.version.minor === minor; } private getCurrentPythonVersionMessage(env?: ResolvedEnvironment): string { - return env?.version && env.environment + return env?.version && + (env.environment || this.isGlobalInterpreter(env)) ? `Current Python version is ${env.version.major}.${env.version.minor}.${env.version.micro}.` : "No active environments found."; } @@ -224,7 +235,10 @@ export class EnvironmentDependenciesVerifier extends MultiStepAccessVerifier { } const expectedPythonVersion = this.getExpectedPythonVersionMessage(dbrVersion); - if (!env?.environment || envVersionTooLow) { + if ( + (!env?.environment && !this.isGlobalInterpreter(env)) || + envVersionTooLow + ) { return this.rejectStep( "checkPythonEnvironment", `Activate an environment with Python ${expectedPythonVersion}`, @@ -235,7 +249,7 @@ export class EnvironmentDependenciesVerifier extends MultiStepAccessVerifier { ); } const executable = await this.pythonExtension.getPythonExecutable(); - if (!executable) { + if (!env || !executable) { return this.rejectStep( "checkPythonEnvironment", `Activate an environment with Python ${expectedPythonVersion}`, @@ -248,9 +262,11 @@ export class EnvironmentDependenciesVerifier extends MultiStepAccessVerifier { env, this.getCurrentPythonVersionMessage(env) ); + const envDisplayName = + env.environment?.name ?? env.executable.uri?.fsPath ?? env.path; return this.acceptStep( "checkPythonEnvironment", - `Active Environment: ${env.environment.name}`, + `Active Environment: ${envDisplayName}`, env.executable.uri?.fsPath, warning ); From a4948297b8f99864f2205ebb71cc6cb55254c4d7 Mon Sep 17 00:00:00 2001 From: twsl <45483159+twsl@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:44:08 +0000 Subject: [PATCH 5/6] Support resolving all known environments --- .../src/language/MsPythonExtensionWrapper.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/databricks-vscode/src/language/MsPythonExtensionWrapper.ts b/packages/databricks-vscode/src/language/MsPythonExtensionWrapper.ts index b7fb0ff69..8a7258dd8 100644 --- a/packages/databricks-vscode/src/language/MsPythonExtensionWrapper.ts +++ b/packages/databricks-vscode/src/language/MsPythonExtensionWrapper.ts @@ -73,6 +73,19 @@ export class MsPythonExtensionWrapper implements Disposable { return filteredEnvs; } + async getAllKnownEnvironments() { + await this.api.environments.refreshEnvironments(); + const resolvedEnvs = []; + for (const env of this.api.environments.known) { + const resolvedEnv = + await this.api.environments.resolveEnvironment(env); + if (resolvedEnv) { + resolvedEnvs.push(env); + } + } + return resolvedEnvs; + } + get onDidChangePythonExecutable(): Event { if (this.api.settings) { return this.api.settings.onDidChangeExecutionDetails; From 3a28a6f7651c4f4f210e604a9ceb9f6dfc2d4a02 Mon Sep 17 00:00:00 2001 From: twsl <45483159+twsl@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:43:47 +0000 Subject: [PATCH 6/6] Add unit tests for environment commands and dependencies verification --- .../src/language/EnvironmentCommands.test.ts | 110 +++++++ .../EnvironmentDependenciesVerifier.test.ts | 301 ++++++++++++++++++ 2 files changed, 411 insertions(+) create mode 100644 packages/databricks-vscode/src/language/EnvironmentCommands.test.ts create mode 100644 packages/databricks-vscode/src/language/EnvironmentDependenciesVerifier.test.ts diff --git a/packages/databricks-vscode/src/language/EnvironmentCommands.test.ts b/packages/databricks-vscode/src/language/EnvironmentCommands.test.ts new file mode 100644 index 000000000..8429f3bcf --- /dev/null +++ b/packages/databricks-vscode/src/language/EnvironmentCommands.test.ts @@ -0,0 +1,110 @@ +import assert from "assert"; +import {Uri} from "vscode"; +import {Environment} from "./MsPythonExtensionApi"; +import {environmentName} from "../utils/environmentUtils"; + +function makeEnvironment(overrides: Partial = {}): Environment { + return { + id: "env-1", + path: "/usr/bin/python3", + executable: { + uri: Uri.file("/usr/bin/python3"), + bitness: "64-bit", + sysPrefix: "/usr", + }, + environment: { + type: "VirtualEnvironment", + name: "test-venv", + folderUri: Uri.file("/home/user/test-venv"), + workspaceFolder: undefined, + }, + version: { + major: 3, + minor: 10, + micro: 0, + release: {level: "final", serial: 0}, + sysVersion: "3.10.0", + }, + tools: [], + ...overrides, + }; +} + +function makeGlobalEnvironment(): Environment { + return makeEnvironment({ + id: "global-python", + path: "/usr/bin/python3", + environment: undefined, + }); +} + +describe(__filename, () => { + it("should use path as name for global environments", () => { + const env = makeGlobalEnvironment(); + const name = environmentName(env); + assert.strictEqual(name, "3.10.0 /usr/bin/python3"); + }); + + it("should use environment name for virtual environments", () => { + const env = makeEnvironment(); + const name = environmentName(env); + assert.strictEqual(name, "3.10.0 test-venv"); + }); + + it("should handle environments without version", () => { + const env = makeGlobalEnvironment(); + (env as any).version = undefined; + const name = environmentName(env); + assert.strictEqual(name, "/usr/bin/python3"); + }); + + it("should identify global environment as having no environment property", () => { + const globalEnv = makeGlobalEnvironment(); + assert.strictEqual(globalEnv.environment, undefined); + assert.ok(globalEnv.version); + assert.ok(globalEnv.executable.uri); + }); + + it("should identify virtual environment as having an environment property", () => { + const venvEnv = makeEnvironment(); + assert.ok(venvEnv.environment); + assert.strictEqual(venvEnv.environment!.type, "VirtualEnvironment"); + assert.strictEqual(venvEnv.environment!.name, "test-venv"); + }); + + it("should distinguish global from non-global by environment field", () => { + const globalEnv = makeGlobalEnvironment(); + const venvEnv = makeEnvironment(); + assert.strictEqual(!globalEnv.environment, true); + assert.strictEqual(!venvEnv.environment, false); + }); + + it("should create pick item with 'Global' description for global environments", () => { + const env = makeGlobalEnvironment(); + const isGlobal = !env.environment; + const item = { + label: environmentName(env), + description: isGlobal ? "Global" : env.environment?.type, + detail: env.path, + path: env.path, + isGlobal, + }; + assert.strictEqual(item.description, "Global"); + assert.strictEqual(item.isGlobal, true); + assert.strictEqual(item.path, "/usr/bin/python3"); + }); + + it("should create pick item with env type for virtual environments", () => { + const env = makeEnvironment(); + const isGlobal = !env.environment; + const item = { + label: environmentName(env), + description: isGlobal ? "Global" : env.environment?.type, + detail: env.path, + path: env.path, + isGlobal, + }; + assert.strictEqual(item.description, "VirtualEnvironment"); + assert.strictEqual(item.isGlobal, false); + }); +}); diff --git a/packages/databricks-vscode/src/language/EnvironmentDependenciesVerifier.test.ts b/packages/databricks-vscode/src/language/EnvironmentDependenciesVerifier.test.ts new file mode 100644 index 000000000..c84ddff89 --- /dev/null +++ b/packages/databricks-vscode/src/language/EnvironmentDependenciesVerifier.test.ts @@ -0,0 +1,301 @@ +import assert from "assert"; +import {mock, when, instance} from "ts-mockito"; +import {Uri} from "vscode"; +import {ConnectionManager} from "../configuration/ConnectionManager"; +import {MsPythonExtensionWrapper} from "./MsPythonExtensionWrapper"; +import {EnvironmentDependenciesInstaller} from "./EnvironmentDependenciesInstaller"; +import {EnvironmentDependenciesVerifier} from "./EnvironmentDependenciesVerifier"; +import {ConfigureAutocomplete} from "./ConfigureAutocomplete"; +import {ResolvedEnvironment} from "./MsPythonExtensionApi"; +import {Cluster} from "../sdk-extensions"; + +function makeResolvedEnv( + overrides: Partial = {} +): ResolvedEnvironment { + return { + id: "env-1", + path: "/home/user/venv/bin/python", + executable: { + uri: Uri.file("/home/user/venv/bin/python"), + bitness: "64-bit", + sysPrefix: "/home/user/venv", + }, + environment: { + type: "VirtualEnvironment", + name: "my-venv", + folderUri: Uri.file("/home/user/venv"), + workspaceFolder: undefined, + }, + version: { + major: 3, + minor: 10, + micro: 0, + release: {level: "final", serial: 0}, + sysVersion: "3.10.0", + }, + tools: [], + ...overrides, + } as ResolvedEnvironment; +} + +function makeGlobalResolvedEnv( + overrides: Partial = {} +): ResolvedEnvironment { + return makeResolvedEnv({ + id: "global-python", + path: "/usr/bin/python3", + executable: { + uri: Uri.file("/usr/bin/python3"), + bitness: "64-bit", + sysPrefix: "/usr", + }, + environment: undefined, + ...overrides, + }); +} + +describe(__filename, () => { + let mockedConnectionManager: ConnectionManager; + let mockedPythonExtension: MsPythonExtensionWrapper; + let mockedInstaller: EnvironmentDependenciesInstaller; + let mockedAutocomplete: ConfigureAutocomplete; + let verifier: EnvironmentDependenciesVerifier; + + beforeEach(() => { + mockedConnectionManager = mock(ConnectionManager); + mockedPythonExtension = mock(MsPythonExtensionWrapper); + mockedInstaller = mock(EnvironmentDependenciesInstaller); + mockedAutocomplete = mock(ConfigureAutocomplete); + + when(mockedConnectionManager.cluster).thenReturn(undefined); + when(mockedConnectionManager.serverless).thenReturn(false); + + when(mockedConnectionManager.onDidChangeCluster).thenReturn((() => ({ + dispose: () => {}, + })) as any); + when(mockedConnectionManager.onDidChangeState).thenReturn((() => ({ + dispose: () => {}, + })) as any); + when(mockedPythonExtension.onDidChangePythonExecutable).thenReturn( + (() => ({dispose: () => {}})) as any + ); + when(mockedInstaller.onDidTryInstallation).thenReturn((() => ({ + dispose: () => {}, + })) as any); + when(mockedAutocomplete.onDidUpdate).thenReturn((() => ({ + dispose: () => {}, + })) as any); + + verifier = new EnvironmentDependenciesVerifier( + instance(mockedConnectionManager), + instance(mockedPythonExtension), + instance(mockedInstaller), + instance(mockedAutocomplete) + ); + }); + + afterEach(() => { + verifier.dispose(); + }); + + it("should accept a global interpreter with correct Python version", async () => { + const globalEnv = makeGlobalResolvedEnv(); + when(mockedPythonExtension.pythonEnvironment).thenReturn( + Promise.resolve(globalEnv) + ); + when(mockedPythonExtension.getPythonExecutable()).thenResolve( + "/usr/bin/python3" + ); + + const mockedCluster = mock(Cluster); + when(mockedCluster.dbrVersion).thenReturn([13, 3]); + when(mockedConnectionManager.cluster).thenReturn( + instance(mockedCluster) + ); + + const result = await verifier.checkPythonEnvironment(); + assert.ok(result.available); + }); + + it("should display executable path as name for global interpreters", async () => { + const globalEnv = makeGlobalResolvedEnv(); + when(mockedPythonExtension.pythonEnvironment).thenReturn( + Promise.resolve(globalEnv) + ); + when(mockedPythonExtension.getPythonExecutable()).thenResolve( + "/usr/bin/python3" + ); + + const result = await verifier.checkPythonEnvironment(); + assert.ok(result.available); + assert.ok(result.title?.includes("/usr/bin/python3")); + }); + + it("should display environment name for virtual environments", async () => { + const venvEnv = makeResolvedEnv(); + when(mockedPythonExtension.pythonEnvironment).thenReturn( + Promise.resolve(venvEnv) + ); + when(mockedPythonExtension.getPythonExecutable()).thenResolve( + "/home/user/venv/bin/python" + ); + + const result = await verifier.checkPythonEnvironment(); + assert.ok(result.available); + assert.ok(result.title?.includes("my-venv")); + }); + + it("should reject global interpreter without version", async () => { + const globalEnv = makeGlobalResolvedEnv({ + version: undefined, + }); + when(mockedPythonExtension.pythonEnvironment).thenReturn( + Promise.resolve(globalEnv) + ); + + const result = await verifier.checkPythonEnvironment(); + assert.ok(!result.available); + }); + + it("should reject global interpreter without executable uri", async () => { + const globalEnv = makeGlobalResolvedEnv({ + executable: { + uri: undefined, + bitness: "64-bit", + sysPrefix: "/usr", + }, + }); + when(mockedPythonExtension.pythonEnvironment).thenReturn( + Promise.resolve(globalEnv) + ); + + const result = await verifier.checkPythonEnvironment(); + assert.ok(!result.available); + }); + + it("should reject when env is undefined", async () => { + when(mockedPythonExtension.pythonEnvironment).thenReturn( + Promise.resolve(undefined) + ); + + const result = await verifier.checkPythonEnvironment(); + assert.ok(!result.available); + }); + + it("should reject global interpreter with Python version too low", async () => { + const globalEnv = makeGlobalResolvedEnv({ + version: { + major: 3, + minor: 9, + micro: 0, + release: {level: "final", serial: 0}, + sysVersion: "3.9.0", + }, + }); + when(mockedPythonExtension.pythonEnvironment).thenReturn( + Promise.resolve(globalEnv) + ); + + const result = await verifier.checkPythonEnvironment(); + assert.ok(!result.available); + }); + + it("should show version mismatch warning for global interpreter with DBR 15", async () => { + const globalEnv = makeGlobalResolvedEnv(); + when(mockedPythonExtension.pythonEnvironment).thenReturn( + Promise.resolve(globalEnv) + ); + when(mockedPythonExtension.getPythonExecutable()).thenResolve( + "/usr/bin/python3" + ); + + const mockedCluster = mock(Cluster); + when(mockedCluster.dbrVersion).thenReturn([15, 1]); + when(mockedConnectionManager.cluster).thenReturn( + instance(mockedCluster) + ); + + const result = await verifier.checkPythonEnvironment(); + assert.ok(result.available); + assert.ok(result.warning); + assert.ok(result.warning!.includes("3.11")); + }); + + it("should not warn when global interpreter matches DBR 15", async () => { + const globalEnv = makeGlobalResolvedEnv({ + version: { + major: 3, + minor: 11, + micro: 2, + release: {level: "final", serial: 0}, + sysVersion: "3.11.2", + }, + }); + when(mockedPythonExtension.pythonEnvironment).thenReturn( + Promise.resolve(globalEnv) + ); + when(mockedPythonExtension.getPythonExecutable()).thenResolve( + "/usr/bin/python3" + ); + + const mockedCluster = mock(Cluster); + when(mockedCluster.dbrVersion).thenReturn([15, 1]); + when(mockedConnectionManager.cluster).thenReturn( + instance(mockedCluster) + ); + + const result = await verifier.checkPythonEnvironment(); + assert.ok(result.available); + assert.strictEqual(result.warning, undefined); + }); + + it("should accept global interpreter for serverless with Python 3.11", async () => { + const globalEnv = makeGlobalResolvedEnv({ + version: { + major: 3, + minor: 11, + micro: 0, + release: {level: "final", serial: 0}, + sysVersion: "3.11.0", + }, + }); + when(mockedPythonExtension.pythonEnvironment).thenReturn( + Promise.resolve(globalEnv) + ); + when(mockedPythonExtension.getPythonExecutable()).thenResolve( + "/usr/bin/python3" + ); + when(mockedConnectionManager.serverless).thenReturn(true); + + const result = await verifier.checkPythonEnvironment(); + assert.ok(result.available); + }); + + it("should reject global interpreter for serverless with Python 3.10", async () => { + const globalEnv = makeGlobalResolvedEnv(); + when(mockedPythonExtension.pythonEnvironment).thenReturn( + Promise.resolve(globalEnv) + ); + when(mockedConnectionManager.serverless).thenReturn(true); + + const result = await verifier.checkPythonEnvironment(); + assert.ok(!result.available); + }); + + it("should reject env without environment and without executable uri", async () => { + const env = makeResolvedEnv({ + environment: undefined, + executable: { + uri: undefined, + bitness: "64-bit", + sysPrefix: "/some/global", + }, + }); + when(mockedPythonExtension.pythonEnvironment).thenReturn( + Promise.resolve(env) + ); + + const result = await verifier.checkPythonEnvironment(); + assert.ok(!result.available); + }); +});