From f763ea4657de13c6f87a059c0140fb07f8fb7f1b Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Mon, 17 Aug 2026 16:38:06 -0700 Subject: [PATCH 1/2] test: enforce headless package conformance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3fd1a810-6840-4ac9-ac33-c8a9fda4bfc4 --- src/managers/builtin/pipPackageManager.ts | 8 +- ...ageManagerHeadlessConformance.unit.test.ts | 76 +++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 src/test/managers/common/packageManagerHeadlessConformance.unit.test.ts diff --git a/src/managers/builtin/pipPackageManager.ts b/src/managers/builtin/pipPackageManager.ts index 836244bb..137cc5db 100644 --- a/src/managers/builtin/pipPackageManager.ts +++ b/src/managers/builtin/pipPackageManager.ts @@ -9,7 +9,6 @@ import { MarkdownString, ProgressLocation, ThemeIcon, - window, } from 'vscode'; import { DidChangePackagesEventArgs, @@ -21,6 +20,7 @@ import { PythonEnvironment, PythonEnvironmentApi, } from '../../api'; +import { showErrorMessage, withProgress } from '../../common/window.apis'; import { updatePackagesAndNotify } from '../common/packageChanges'; import { runPython, runUV, shouldUseUv } from './helpers'; import { getWorkspacePackagesToInstall } from './pipUtils'; @@ -74,7 +74,7 @@ export class PipPackageManager implements PackageManager, Disposable { install: toInstall, uninstall: toUninstall, }; - await window.withProgress( + await withProgress( { location: ProgressLocation.Notification, title: 'Installing packages', @@ -99,7 +99,7 @@ export class PipPackageManager implements PackageManager, Disposable { this.log.error('Error managing packages', e); if (!manageOptions.runHeadless) { setImmediate(async () => { - const result = await window.showErrorMessage('Error managing packages', 'View Output'); + const result = await showErrorMessage('Error managing packages', 'View Output'); if (result === 'View Output') { this.log.show(); } @@ -112,7 +112,7 @@ export class PipPackageManager implements PackageManager, Disposable { } async refresh(environment: PythonEnvironment): Promise { - await window.withProgress( + await withProgress( { location: ProgressLocation.Window, title: 'Refreshing packages', diff --git a/src/test/managers/common/packageManagerHeadlessConformance.unit.test.ts b/src/test/managers/common/packageManagerHeadlessConformance.unit.test.ts new file mode 100644 index 00000000..50ece740 --- /dev/null +++ b/src/test/managers/common/packageManagerHeadlessConformance.unit.test.ts @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { LogOutputChannel, Uri } from 'vscode'; +import { PackageManager, PythonEnvironment, PythonEnvironmentApi } from '../../../api'; +import * as errorUtils from '../../../common/errors/utils'; +import * as windowApis from '../../../common/window.apis'; +import { PipPackageManager } from '../../../managers/builtin/pipPackageManager'; +import * as pipUtils from '../../../managers/builtin/pipUtils'; +import * as builtinUtils from '../../../managers/builtin/utils'; +import { VenvManager } from '../../../managers/builtin/venvManager'; +import { CondaPackageManager } from '../../../managers/conda/condaPackageManager'; +import * as condaUtils from '../../../managers/conda/condaUtils'; +import { PoetryManager } from '../../../managers/poetry/poetryManager'; +import { PoetryPackageManager } from '../../../managers/poetry/poetryPackageManager'; +import * as poetryUtils from '../../../managers/poetry/poetryUtils'; + +suite('Package manager headless conformance', () => { + const environment = { + envId: { id: 'test-environment', managerId: 'test-manager' }, + environmentPath: Uri.file('/path/to/environment'), + } as PythonEnvironment; + + teardown(() => { + sinon.restore(); + }); + + function createManagers(): PackageManager[] { + const api = {} as PythonEnvironmentApi; + const log = { + error: sinon.stub(), + info: sinon.stub(), + show: sinon.stub(), + } as unknown as LogOutputChannel; + return [ + new PipPackageManager(api, log, { getProjectsByEnvironment: sinon.stub().returns([]) } as unknown as VenvManager), + new CondaPackageManager(api, log), + new PoetryPackageManager(api, log, {} as PoetryManager), + ]; + } + + test('does not invoke interactive package input when no packages are provided', async () => { + const pipPicker = sinon.stub(pipUtils, 'getWorkspacePackagesToInstall'); + const condaPicker = sinon.stub(condaUtils, 'getCommonCondaPackagesToInstall'); + const poetryInput = sinon.stub(windowApis, 'showInputBox'); + + for (const manager of createManagers()) { + await manager.manage(environment, { install: [], runHeadless: true }); + } + + assert.ok(pipPicker.notCalled); + assert.ok(condaPicker.notCalled); + assert.ok(poetryInput.notCalled); + }); + + test('rejects failures without showing error notifications', async () => { + const operationError = new Error('package operation failed'); + sinon.stub(windowApis, 'withProgress').callsFake(async (_options, task) => task({} as never, {} as never)); + sinon.stub(builtinUtils, 'managePackages').rejects(operationError); + sinon.stub(condaUtils, 'managePackages').rejects(operationError); + sinon.stub(poetryUtils, 'getPoetry').resolves(undefined); + const showErrorMessage = sinon.stub(windowApis, 'showErrorMessage').resolves(undefined); + const showErrorMessageWithLogs = sinon.stub(errorUtils, 'showErrorMessageWithLogs').resolves(); + + for (const manager of createManagers()) { + await assert.rejects( + manager.manage(environment, { install: ['requests'], runHeadless: true }), + ); + } + + assert.ok(showErrorMessage.notCalled); + assert.ok(showErrorMessageWithLogs.notCalled); + }); +}); From cb05041fd81dff30f34d9710c98660586f8e469e Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Mon, 17 Aug 2026 17:03:31 -0700 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../common/packageManagerHeadlessConformance.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/managers/common/packageManagerHeadlessConformance.unit.test.ts b/src/test/managers/common/packageManagerHeadlessConformance.unit.test.ts index 50ece740..30b0a317 100644 --- a/src/test/managers/common/packageManagerHeadlessConformance.unit.test.ts +++ b/src/test/managers/common/packageManagerHeadlessConformance.unit.test.ts @@ -20,7 +20,7 @@ import * as poetryUtils from '../../../managers/poetry/poetryUtils'; suite('Package manager headless conformance', () => { const environment = { envId: { id: 'test-environment', managerId: 'test-manager' }, - environmentPath: Uri.file('/path/to/environment'), + environmentPath: Uri.joinPath(Uri.file(__dirname), 'path', 'to', 'environment'), } as PythonEnvironment; teardown(() => {