Skip to content

Commit b027038

Browse files
committed
Address PR review comments: task listener cleanup, use wrappers, add await, handle empty envs
1 parent 0cc1ea6 commit b027038

File tree

6 files changed

+47
-25
lines changed

6 files changed

+47
-25
lines changed

src/common/tasks.apis.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
import { Task, TaskExecution, tasks } from 'vscode';
1+
import { Disposable, Task, TaskExecution, TaskProcessEndEvent, tasks } from 'vscode';
22

33
export async function executeTask(task: Task): Promise<TaskExecution> {
44
return tasks.executeTask(task);
55
}
6+
7+
export function onDidEndTaskProcess(
8+
listener: (e: TaskProcessEndEvent) => unknown,
9+
thisArgs?: unknown,
10+
disposables?: Disposable[],
11+
): Disposable {
12+
return tasks.onDidEndTaskProcess(listener, thisArgs, disposables);
13+
}

src/managers/builtin/sysPythonManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class SysPythonManager implements EnvironmentManager {
7575

7676
// If no Python environments were found, offer to install via uv
7777
if (this.collection.length === 0) {
78-
promptInstallPythonViaUv('activation', this.api, this.log);
78+
await promptInstallPythonViaUv('activation', this.api, this.log);
7979
}
8080

8181
this._initialized.resolve();

src/managers/builtin/uvPythonInstaller.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
1-
import {
2-
LogOutputChannel,
3-
ProgressLocation,
4-
ShellExecution,
5-
Task,
6-
TaskPanelKind,
7-
TaskRevealKind,
8-
TaskScope,
9-
tasks,
10-
window,
11-
} from 'vscode';
1+
import { LogOutputChannel, ProgressLocation, ShellExecution, Task, TaskPanelKind, TaskRevealKind, TaskScope } from 'vscode';
122
import { PythonEnvironmentApi } from '../../api';
133
import { spawnProcess } from '../../common/childProcess.apis';
144
import { UvInstallStrings } from '../../common/localize';
155
import { traceError, traceInfo, traceLog } from '../../common/logging';
166
import { getGlobalPersistentState } from '../../common/persistentState';
17-
import { executeTask } from '../../common/tasks.apis';
7+
import { executeTask, onDidEndTaskProcess } from '../../common/tasks.apis';
188
import { EventNames } from '../../common/telemetry/constants';
199
import { sendTelemetryEvent } from '../../common/telemetry/sender';
2010
import { createDeferred } from '../../common/utils/deferred';
2111
import { isWindows } from '../../common/utils/platformUtils';
22-
import { showInformationMessage } from '../../common/window.apis';
12+
import { showErrorMessage, showInformationMessage, withProgress } from '../../common/window.apis';
2313
import { isUvInstalled, resetUvInstallationCache } from './helpers';
2414

2515
const UV_INSTALL_PYTHON_DONT_ASK_KEY = 'python-envs:uv:UV_INSTALL_PYTHON_DONT_ASK';
@@ -72,6 +62,9 @@ async function getUvInstallCommand(): Promise<{ executable: string; args: string
7262
};
7363
}
7464

65+
// Timeout for task completion (5 minutes)
66+
const TASK_TIMEOUT_MS = 5 * 60 * 1000;
67+
7568
/**
7669
* Runs a shell command as a visible VS Code task and waits for completion.
7770
* @param name Task name displayed in the UI
@@ -92,20 +85,29 @@ async function runTaskAndWait(name: string, executable: string, args: string[]):
9285

9386
const deferred = createDeferred<boolean>();
9487

95-
const disposable = tasks.onDidEndTaskProcess((e) => {
88+
const disposable = onDidEndTaskProcess((e) => {
9689
if (e.execution.task === task) {
97-
disposable.dispose();
9890
deferred.resolve(e.exitCode === 0);
9991
}
10092
});
10193

94+
// Set up timeout to prevent indefinite waiting
95+
const timeoutId = setTimeout(() => {
96+
if (!deferred.completed) {
97+
traceError(`Task "${name}" timed out after ${TASK_TIMEOUT_MS / 1000} seconds`);
98+
deferred.resolve(false);
99+
}
100+
}, TASK_TIMEOUT_MS);
101+
102102
try {
103103
await executeTask(task);
104104
return await deferred.promise;
105105
} catch (err) {
106-
disposable.dispose();
107106
traceError(`Task "${name}" failed:`, err);
108107
return false;
108+
} finally {
109+
clearTimeout(timeoutId);
110+
disposable.dispose();
109111
}
110112
}
111113

@@ -218,7 +220,7 @@ export async function installPythonWithUv(api: PythonEnvironmentApi, log?: LogOu
218220

219221
sendTelemetryEvent(EventNames.UV_PYTHON_INSTALL_STARTED, undefined, { uvAlreadyInstalled: uvInstalled });
220222

221-
return await window.withProgress(
223+
return await withProgress(
222224
{
223225
location: ProgressLocation.Notification,
224226
title: UvInstallStrings.installingPython,
@@ -232,7 +234,7 @@ export async function installPythonWithUv(api: PythonEnvironmentApi, log?: LogOu
232234
const uvSuccess = await installUv(log);
233235
if (!uvSuccess) {
234236
sendTelemetryEvent(EventNames.UV_PYTHON_INSTALL_FAILED, undefined, { stage: 'uvInstall' });
235-
window.showErrorMessage(UvInstallStrings.uvInstallFailed);
237+
showErrorMessage(UvInstallStrings.uvInstallFailed);
236238
return false;
237239
}
238240
}
@@ -241,7 +243,7 @@ export async function installPythonWithUv(api: PythonEnvironmentApi, log?: LogOu
241243
const pythonSuccess = await installPythonViaUv(log);
242244
if (!pythonSuccess) {
243245
sendTelemetryEvent(EventNames.UV_PYTHON_INSTALL_FAILED, undefined, { stage: 'pythonInstall' });
244-
window.showErrorMessage(UvInstallStrings.installFailed);
246+
showErrorMessage(UvInstallStrings.installFailed);
245247
return false;
246248
}
247249

@@ -250,7 +252,7 @@ export async function installPythonWithUv(api: PythonEnvironmentApi, log?: LogOu
250252
await api.refreshEnvironments(undefined);
251253

252254
sendTelemetryEvent(EventNames.UV_PYTHON_INSTALL_COMPLETED);
253-
window.showInformationMessage(UvInstallStrings.installComplete);
255+
showInformationMessage(UvInstallStrings.installComplete);
254256

255257
return true;
256258
},

src/managers/builtin/venvManager.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,13 @@ export class VenvManager implements EnvironmentManager {
151151
if (installed) {
152152
// Re-fetch environments after installation
153153
globals = await this.api.getEnvironments('global');
154-
// Update globalEnv reference
155-
this.globalEnv = getLatest(globals.filter((e) => e.version.startsWith('3.')));
154+
// Update globalEnv reference if we found any Python 3.x environments
155+
const python3Envs = globals.filter((e) => e.version.startsWith('3.'));
156+
if (python3Envs.length === 0) {
157+
this.log.warn('Python installed via uv but no Python 3.x global environments were detected.');
158+
} else {
159+
this.globalEnv = getLatest(python3Envs);
160+
}
156161
}
157162
}
158163

src/test/managers/builtin/uvPythonInstaller.unit.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,8 @@ suite('uvPythonInstaller - isDontAskAgainSet and clearDontAskAgain', () => {
168168
assert(mockState.set.calledWith('python-envs:uv:UV_INSTALL_PYTHON_DONT_ASK', false), 'Should clear the flag');
169169
});
170170
});
171+
172+
// NOTE: Installation functions (installUv, installPythonViaUv, installPythonWithUv) require
173+
// VS Code's Task API which cannot be fully mocked in unit tests.
174+
// These should be tested via integration tests in a real VS Code environment.
175+

src/test/mocks/vsc/extHostedTypes.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1706,7 +1706,9 @@ export class ShellExecution implements vscode.ShellExecution {
17061706
// }
17071707
// }
17081708
// return hash.digest('hex');
1709-
throw new Error('Not spported');
1709+
// Return a simple unique ID based on command
1710+
const cmd = typeof this._command === 'string' ? this._command : this._command?.value ?? '';
1711+
return `shell-${cmd}-${Date.now()}`;
17101712
}
17111713
}
17121714

0 commit comments

Comments
 (0)