-
Notifications
You must be signed in to change notification settings - Fork 56
Add package manager command smoke tests #1677
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
Open
+296
−1
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| import assert from 'assert'; | ||
| import * as sinon from 'sinon'; | ||
| import { LogOutputChannel } from 'vscode'; | ||
| import * as workspaceApis from '../../../common/workspace.apis'; | ||
| import { PipAvailableVersionsCommand, UvAvailableVersionsCommand } from '../../../managers/builtin/commands/availableVersions'; | ||
| import { PipInstallCommand, UvInstallCommand } from '../../../managers/builtin/commands/install'; | ||
| import { PipListCommand, UvListCommand } from '../../../managers/builtin/commands/list'; | ||
| import { PipListDirectNamesCommand, UvListDirectNamesCommand } from '../../../managers/builtin/commands/listDirectNames'; | ||
| import { PipUninstallCommand, UvUninstallCommand } from '../../../managers/builtin/commands/uninstall'; | ||
| import { PipVersionCommand, UvVersionCommand } from '../../../managers/builtin/commands/version'; | ||
| import * as helpers from '../../../managers/builtin/helpers'; | ||
| import { createMockLogOutputChannel } from '../../mocks/helper'; | ||
|
|
||
| suite('Pip and UV commands', () => { | ||
| let mockLog: LogOutputChannel; | ||
| let runPythonStub: sinon.SinonStub; | ||
| let runUvStub: sinon.SinonStub; | ||
|
|
||
| setup(() => { | ||
| mockLog = createMockLogOutputChannel(); | ||
| sinon.stub(workspaceApis, 'getConfiguration').returns({ | ||
| get: () => undefined, | ||
| } as unknown as ReturnType<typeof workspaceApis.getConfiguration>); | ||
| runPythonStub = sinon.stub(helpers, 'runPython').resolves(''); | ||
| runUvStub = sinon.stub(helpers, 'runUV').resolves(''); | ||
| }); | ||
|
|
||
| teardown(() => { | ||
| sinon.restore(); | ||
| }); | ||
|
|
||
| // Mutation commands have no output to parse; unit coverage is a smoke test that | ||
| // execute() resolves. Their concrete command strings are exercised by integration tests. | ||
| suite('mutation commands execute without error', () => { | ||
| test('PipInstallCommand', async () => { | ||
| const command = new PipInstallCommand({ pythonExecutable: 'python', log: mockLog }); | ||
| await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); | ||
| }); | ||
|
|
||
| test('UvInstallCommand', async () => { | ||
| const command = new UvInstallCommand({ pythonExecutable: 'python', log: mockLog }); | ||
| await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); | ||
| }); | ||
|
|
||
| test('PipUninstallCommand', async () => { | ||
| const command = new PipUninstallCommand({ pythonExecutable: 'python', log: mockLog }); | ||
| await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); | ||
| }); | ||
|
|
||
| test('UvUninstallCommand', async () => { | ||
| const command = new UvUninstallCommand({ pythonExecutable: 'python', log: mockLog }); | ||
| await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); | ||
| }); | ||
| }); | ||
|
|
||
| test('PipAvailableVersionsCommand parses versions and filters prereleases', async () => { | ||
| runPythonStub.resolves(JSON.stringify({ versions: ['2.0.0rc1', '1.0.0'] })); | ||
| const command = new PipAvailableVersionsCommand({ pythonExecutable: 'python', log: mockLog }); | ||
|
|
||
| const result = await command.execute({ | ||
| packageName: 'package', | ||
| pythonVersion: '3.13.1', | ||
| includePrerelease: false, | ||
| }); | ||
|
|
||
| assert.deepStrictEqual(result.map((v) => v.public), ['1.0.0']); | ||
| }); | ||
|
|
||
| test('UvAvailableVersionsCommand parses versions from embedded JSON', async () => { | ||
| runUvStub.resolves(`Some preamble\n${JSON.stringify({ versions: ['1.0.0'] })}`); | ||
| const command = new UvAvailableVersionsCommand({ pythonExecutable: 'python', log: mockLog }); | ||
|
|
||
| const result = await command.execute({ packageName: 'package', pythonVersion: '3.13.1' }); | ||
|
|
||
| assert.deepStrictEqual(result.map((v) => v.public), ['1.0.0']); | ||
| }); | ||
|
|
||
| test('PipListCommand parses packages and drops entries missing a version', async () => { | ||
| runPythonStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }, { name: 'broken' }])); | ||
| const command = new PipListCommand({ pythonExecutable: 'python', log: mockLog }); | ||
|
|
||
| const result = await command.execute(); | ||
|
|
||
| assert.deepStrictEqual( | ||
| result.map((p) => ({ name: p.name, version: p.version })), | ||
| [{ name: 'package', version: '1.0.0' }], | ||
| ); | ||
| }); | ||
|
|
||
| test('UvListCommand parses packages from JSON', async () => { | ||
| runUvStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }])); | ||
| const command = new UvListCommand({ pythonExecutable: 'python', log: mockLog }); | ||
|
|
||
| const result = await command.execute(); | ||
|
|
||
| assert.deepStrictEqual( | ||
| result.map((p) => ({ name: p.name, version: p.version })), | ||
| [{ name: 'package', version: '1.0.0' }], | ||
| ); | ||
| }); | ||
|
|
||
| test('PipListDirectNamesCommand returns normalized names', async () => { | ||
| runPythonStub.resolves(JSON.stringify([{ name: 'Flask_Thing', version: '1.0.0' }])); | ||
| const command = new PipListDirectNamesCommand({ pythonExecutable: 'python', log: mockLog }); | ||
|
|
||
| const result = await command.execute(); | ||
|
|
||
| assert.deepStrictEqual([...result], ['flask-thing']); | ||
| }); | ||
|
|
||
| test('UvListDirectNamesCommand keeps top-level packages and skips indented dependencies', async () => { | ||
| runUvStub.resolves(['Flask_Thing 1.0.0', '├── dependency 2.0.0', '└── another-dep 3.0.0'].join('\n')); | ||
| const command = new UvListDirectNamesCommand({ pythonExecutable: 'python', log: mockLog }); | ||
|
|
||
| const result = await command.execute(); | ||
|
|
||
| assert.deepStrictEqual([...result], ['flask-thing']); | ||
| }); | ||
|
|
||
| test('PipVersionCommand parses the version from pip --version output', async () => { | ||
| runPythonStub.resolves('pip 24.0 from /site-packages/pip (python 3.13)'); | ||
| const command = new PipVersionCommand({ pythonExecutable: 'python', log: mockLog }); | ||
|
|
||
| const result = await command.execute(); | ||
|
|
||
| assert.strictEqual(result?.public, '24.0'); | ||
| }); | ||
|
|
||
| test('UvVersionCommand parses the version from uv --version output', async () => { | ||
| runUvStub.resolves('uv 0.4.20 (abcdef 2024-01-01)'); | ||
| const command = new UvVersionCommand({ pythonExecutable: 'python', log: mockLog }); | ||
|
|
||
| const result = await command.execute(); | ||
|
|
||
| assert.strictEqual(result?.public, '0.4.20'); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import assert from 'assert'; | ||
| import * as sinon from 'sinon'; | ||
| import { LogOutputChannel } from 'vscode'; | ||
| import * as workspaceApis from '../../../common/workspace.apis'; | ||
| import { CondaAvailableVersionsCommand } from '../../../managers/conda/commands/availableVersions'; | ||
| import { CondaInstallCommand } from '../../../managers/conda/commands/install'; | ||
| import { CondaListCommand } from '../../../managers/conda/commands/list'; | ||
| import { CondaUninstallCommand } from '../../../managers/conda/commands/uninstall'; | ||
| import { CondaVersionCommand } from '../../../managers/conda/commands/version'; | ||
| import * as condaUtils from '../../../managers/conda/condaUtils'; | ||
| import { createMockLogOutputChannel } from '../../mocks/helper'; | ||
|
|
||
| suite('Conda commands', () => { | ||
| let mockLog: LogOutputChannel; | ||
| let runCondaStub: sinon.SinonStub; | ||
|
|
||
| setup(() => { | ||
| mockLog = createMockLogOutputChannel(); | ||
| sinon.stub(workspaceApis, 'getConfiguration').returns({ | ||
| get: () => undefined, | ||
| } as unknown as ReturnType<typeof workspaceApis.getConfiguration>); | ||
| runCondaStub = sinon.stub(condaUtils, 'runCondaExecutable').resolves(''); | ||
| }); | ||
|
|
||
| teardown(() => { | ||
| sinon.restore(); | ||
| }); | ||
|
|
||
| // Mutation commands have no output to parse; unit coverage is a smoke test that | ||
| // execute() resolves. Their concrete command strings are exercised by integration tests. | ||
| suite('mutation commands execute without error', () => { | ||
| test('CondaInstallCommand', async () => { | ||
| const command = new CondaInstallCommand({ | ||
| pythonExecutable: 'conda', | ||
| condaEnvironmentPath: 'environment', | ||
| log: mockLog, | ||
| }); | ||
| await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); | ||
| }); | ||
|
|
||
| test('CondaUninstallCommand', async () => { | ||
| const command = new CondaUninstallCommand({ | ||
| pythonExecutable: 'conda', | ||
| condaEnvironmentPath: 'environment', | ||
| log: mockLog, | ||
| }); | ||
| await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); | ||
| }); | ||
| }); | ||
|
|
||
| test('CondaAvailableVersionsCommand parses versions keyed by package name', async () => { | ||
| runCondaStub.resolves(JSON.stringify({ package: [{ version: '1.0.0' }, { version: '2.0.0' }] })); | ||
| const command = new CondaAvailableVersionsCommand({ pythonExecutable: 'conda', log: mockLog }); | ||
|
|
||
| const result = await command.execute({ packageName: 'package', pythonVersion: '' }); | ||
|
|
||
| assert.deepStrictEqual(result.map((v) => v.public), ['1.0.0', '2.0.0']); | ||
| }); | ||
|
|
||
| test('CondaListCommand parses packages and drops entries missing a version', async () => { | ||
| runCondaStub.resolves(JSON.stringify([{ name: 'package', version: '1.0.0' }, { name: 'broken' }])); | ||
| const command = new CondaListCommand({ | ||
| pythonExecutable: 'conda', | ||
| condaEnvironmentPath: 'environment', | ||
| log: mockLog, | ||
| }); | ||
|
|
||
| const result = await command.execute(); | ||
|
|
||
| assert.deepStrictEqual( | ||
| result.map((p) => ({ name: p.name, version: p.version })), | ||
| [{ name: 'package', version: '1.0.0' }], | ||
| ); | ||
| }); | ||
|
|
||
| test('CondaVersionCommand parses the version from conda --version output', async () => { | ||
| runCondaStub.resolves('conda 24.1.2'); | ||
| const command = new CondaVersionCommand({ pythonExecutable: 'conda', log: mockLog }); | ||
|
|
||
| const result = await command.execute(); | ||
|
|
||
| assert.strictEqual(result?.public, '24.1.2'); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import assert from 'assert'; | ||
| import * as sinon from 'sinon'; | ||
| import { LogOutputChannel } from 'vscode'; | ||
| import * as workspaceApis from '../../../common/workspace.apis'; | ||
| import { PoetryAddCommand } from '../../../managers/poetry/commands/add'; | ||
| import { PoetryRemoveCommand } from '../../../managers/poetry/commands/remove'; | ||
| import * as poetryRunner from '../../../managers/poetry/commands/runPoetry'; | ||
| import { PoetryShowCommand } from '../../../managers/poetry/commands/show'; | ||
| import { PoetryShowTopLevelCommand } from '../../../managers/poetry/commands/showTopLevel'; | ||
| import { PoetryVersionCommand } from '../../../managers/poetry/commands/version'; | ||
| import * as poetryUtils from '../../../managers/poetry/poetryUtils'; | ||
| import { createMockLogOutputChannel } from '../../mocks/helper'; | ||
|
|
||
| suite('Poetry commands', () => { | ||
| let mockLog: LogOutputChannel; | ||
| let runPoetryStub: sinon.SinonStub; | ||
|
|
||
| setup(() => { | ||
| mockLog = createMockLogOutputChannel(); | ||
| sinon.stub(workspaceApis, 'getConfiguration').returns({ | ||
| get: () => undefined, | ||
| } as unknown as ReturnType<typeof workspaceApis.getConfiguration>); | ||
| runPoetryStub = sinon.stub(poetryRunner, 'runPoetry').resolves(''); | ||
| sinon.stub(poetryUtils, 'getPoetryVersion').resolves('1.8.2'); | ||
| }); | ||
|
|
||
| teardown(() => { | ||
| sinon.restore(); | ||
| }); | ||
|
|
||
| // Mutation commands have no output to parse; unit coverage is a smoke test that | ||
| // execute() resolves. Their concrete command strings are exercised by integration tests. | ||
| suite('mutation commands execute without error', () => { | ||
| test('PoetryAddCommand', async () => { | ||
| const command = new PoetryAddCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); | ||
| await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); | ||
| }); | ||
|
|
||
| test('PoetryRemoveCommand', async () => { | ||
| const command = new PoetryRemoveCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); | ||
| await assert.doesNotReject(() => command.execute({ packages: [{ packageName: 'package' }] })); | ||
| }); | ||
| }); | ||
|
|
||
| test('PoetryShowCommand parses name, version and description', async () => { | ||
| runPoetryStub.resolves(['requests 2.31.0 Python HTTP for Humans.', ''].join('\n')); | ||
| const command = new PoetryShowCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); | ||
|
|
||
| const result = await command.execute(); | ||
|
|
||
| assert.deepStrictEqual( | ||
| result.map((p) => ({ name: p.name, version: p.version, description: p.description })), | ||
| [{ name: 'requests', version: '2.31.0', description: '2.31.0 - Python HTTP for Humans.' }], | ||
| ); | ||
| }); | ||
|
|
||
| test('PoetryShowTopLevelCommand returns normalized names', async () => { | ||
| runPoetryStub.resolves(['Flask_Thing', 'requests'].join('\n')); | ||
| const command = new PoetryShowTopLevelCommand({ pythonExecutable: 'poetry', cwd: 'project', log: mockLog }); | ||
|
|
||
| const result = await command.execute(); | ||
|
|
||
| assert.deepStrictEqual([...result], ['flask-thing', 'requests']); | ||
| }); | ||
|
|
||
| test('PoetryVersionCommand parses the poetry version', async () => { | ||
| const command = new PoetryVersionCommand({ pythonExecutable: 'poetry', log: mockLog }); | ||
|
|
||
| const result = await command.execute(); | ||
|
|
||
| assert.strictEqual(result?.public, '1.8.2'); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
This integration-coverage claim is not currently true.
packageManagement.integration.test.tsonly selectsvenvenvironments for install/uninstall, so it never exercises the Conda or Poetry command classes; that path also dispatches to Pip or UV dynamically, so it does not deterministically cover either concrete command string. Because these unit tests do not inspect the runner invocation either, subcommand, flag, interpreter, environment-prefix, andcwdregressions remain uncovered. Please assert the runner arguments in these suites (including the analogous Conda/Poetry tests), or remove this claim until deterministic integration coverage exists.