Skip to content

Commit e6d1a7e

Browse files
committed
test: enforce headless package conformance
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3fd1a810-6840-4ac9-ac33-c8a9fda4bfc4
1 parent 3a4f358 commit e6d1a7e

2 files changed

Lines changed: 80 additions & 4 deletions

File tree

src/managers/builtin/pipPackageManager.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
MarkdownString,
1010
ProgressLocation,
1111
ThemeIcon,
12-
window,
1312
} from 'vscode';
1413
import {
1514
DidChangePackagesEventArgs,
@@ -21,6 +20,7 @@ import {
2120
PythonEnvironment,
2221
PythonEnvironmentApi,
2322
} from '../../api';
23+
import { showErrorMessage, withProgress } from '../../common/window.apis';
2424
import { updatePackagesAndNotify } from '../common/packageChanges';
2525
import { runPython, runUV, shouldUseUv } from './helpers';
2626
import { getWorkspacePackagesToInstall } from './pipUtils';
@@ -74,7 +74,7 @@ export class PipPackageManager implements PackageManager, Disposable {
7474
install: toInstall,
7575
uninstall: toUninstall,
7676
};
77-
await window.withProgress(
77+
await withProgress(
7878
{
7979
location: ProgressLocation.Notification,
8080
title: 'Installing packages',
@@ -99,7 +99,7 @@ export class PipPackageManager implements PackageManager, Disposable {
9999
this.log.error('Error managing packages', e);
100100
if (!manageOptions.runHeadless) {
101101
setImmediate(async () => {
102-
const result = await window.showErrorMessage('Error managing packages', 'View Output');
102+
const result = await showErrorMessage('Error managing packages', 'View Output');
103103
if (result === 'View Output') {
104104
this.log.show();
105105
}
@@ -112,7 +112,7 @@ export class PipPackageManager implements PackageManager, Disposable {
112112
}
113113

114114
async refresh(environment: PythonEnvironment): Promise<void> {
115-
await window.withProgress(
115+
await withProgress(
116116
{
117117
location: ProgressLocation.Window,
118118
title: 'Refreshing packages',
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import * as assert from 'assert';
5+
import * as sinon from 'sinon';
6+
import { LogOutputChannel, Uri } from 'vscode';
7+
import { PackageManager, PythonEnvironment, PythonEnvironmentApi } from '../../../api';
8+
import * as errorUtils from '../../../common/errors/utils';
9+
import * as windowApis from '../../../common/window.apis';
10+
import { PipPackageManager } from '../../../managers/builtin/pipPackageManager';
11+
import * as pipUtils from '../../../managers/builtin/pipUtils';
12+
import * as builtinUtils from '../../../managers/builtin/utils';
13+
import { VenvManager } from '../../../managers/builtin/venvManager';
14+
import { CondaPackageManager } from '../../../managers/conda/condaPackageManager';
15+
import * as condaUtils from '../../../managers/conda/condaUtils';
16+
import { PoetryManager } from '../../../managers/poetry/poetryManager';
17+
import { PoetryPackageManager } from '../../../managers/poetry/poetryPackageManager';
18+
import * as poetryUtils from '../../../managers/poetry/poetryUtils';
19+
20+
suite('Package manager headless conformance', () => {
21+
const environment = {
22+
envId: { id: 'test-environment', managerId: 'test-manager' },
23+
environmentPath: Uri.file('/path/to/environment'),
24+
} as PythonEnvironment;
25+
26+
teardown(() => {
27+
sinon.restore();
28+
});
29+
30+
function createManagers(): PackageManager[] {
31+
const api = {} as PythonEnvironmentApi;
32+
const log = {
33+
error: sinon.stub(),
34+
info: sinon.stub(),
35+
show: sinon.stub(),
36+
} as unknown as LogOutputChannel;
37+
return [
38+
new PipPackageManager(api, log, { getProjectsByEnvironment: sinon.stub().returns([]) } as unknown as VenvManager),
39+
new CondaPackageManager(api, log),
40+
new PoetryPackageManager(api, log, {} as PoetryManager),
41+
];
42+
}
43+
44+
test('does not invoke interactive package input when no packages are provided', async () => {
45+
const pipPicker = sinon.stub(pipUtils, 'getWorkspacePackagesToInstall');
46+
const condaPicker = sinon.stub(condaUtils, 'getCommonCondaPackagesToInstall');
47+
const poetryInput = sinon.stub(windowApis, 'showInputBox');
48+
49+
for (const manager of createManagers()) {
50+
await manager.manage(environment, { install: [], runHeadless: true });
51+
}
52+
53+
assert.ok(pipPicker.notCalled);
54+
assert.ok(condaPicker.notCalled);
55+
assert.ok(poetryInput.notCalled);
56+
});
57+
58+
test('rejects failures without showing error notifications', async () => {
59+
const operationError = new Error('package operation failed');
60+
sinon.stub(windowApis, 'withProgress').callsFake(async (_options, task) => task({} as never, {} as never));
61+
sinon.stub(builtinUtils, 'managePackages').rejects(operationError);
62+
sinon.stub(condaUtils, 'managePackages').rejects(operationError);
63+
sinon.stub(poetryUtils, 'getPoetry').resolves(undefined);
64+
const showErrorMessage = sinon.stub(windowApis, 'showErrorMessage').resolves(undefined);
65+
const showErrorMessageWithLogs = sinon.stub(errorUtils, 'showErrorMessageWithLogs').resolves();
66+
67+
for (const manager of createManagers()) {
68+
await assert.rejects(
69+
manager.manage(environment, { install: ['requests'], runHeadless: true }),
70+
);
71+
}
72+
73+
assert.ok(showErrorMessage.notCalled);
74+
assert.ok(showErrorMessageWithLogs.notCalled);
75+
});
76+
});

0 commit comments

Comments
 (0)