microsoft / microsoft/vscode-python-environments

Improve testability of environment managers by creating a mock/fake NativePythonFinder

Đang mở
#1,159 1 bình luận 0 reaction 1 người được giao Xem trên GitHub

@karthiknadig đang làm issue này rồi.

Từ ngày 3/2/2026.

enhancement testing
Ngôn ngữ chính
TypeScript
Star
138
Fork
62
Merge trung bình
1 ngày 4 giờ
Pull request đã merge (30 ngày)
35

Mô tả

# Problem

The environment managers (Conda, Pyenv, Pipenv, Poetry, Venv, SysPython) lack comprehensive unit tests because testing them is complex. The main barrier is the `NativePythonFinder` dependency which:

1. Spawns an external native binary (`pet.exe`/`pet`) via JSON-RPC
2. Requires real file system operations
3. Depends on actual Python installations being present

Currently, only helper utilities within managers have tests (e.g., `pipUtils`, `venvUtils`, `installArgs`), but the manager classes themselves (`CondaEnvManager`, `VenvManager`, `SysPythonManager`, `PyenvManager`, `PipenvManager`, `PoetryManager`) are untested.

## Current State

- **Tested**: Helper functions in `src/test/managers/builtin/` (pipUtils, venvUtils, etc.)
- **Untested**: Core manager classes in `src/managers/*/`
- **Limited mocking**: `NativePythonFinder` is only partially mocked in `interpreterSelection.unit.test.ts` for the `resolve()` method

## Proposed Solution

### 1. Create a Mock/Fake NativePythonFinder

Create a reusable `MockNativePythonFinder` class in `src/test/mocks/mockNativePythonFinder.ts`:

```typescript
import * as sinon from 'sinon';
import { NativePythonFinder, NativeInfo, NativeEnvInfo, NativePythonEnvironmentKind } from '../../managers/common/nativePythonFinder';
import { Uri } from 'vscode';

export interface MockNativeFinderOptions {
environments?: NativeEnvInfo[];
managers?: { tool: string; executable: string; version?: string }[];
resolveResults?: Map;
}

export function createMockNativePythonFinder(options: MockNativeFinderOptions = {}): NativePythonFinder {
const { environments = [], managers = [], resolveResults = new Map() } = options;

return {
refresh: sinon.stub().callsFake(async (_hardRefresh: boolean, filterOptions?: NativePythonEnvironmentKind | Uri[]): Promise => {
// Filter by kind if specified
if (typeof filterOptions === 'string') {
return environments.filter(e => e.kind === filterOptions);
}
return [...environments, ...managers];
}),
resolve: sinon.stub().callsFake(async (executable: string): Promise => {
const result = resolveResults.get(executable);
if (!result) {
throw new Error(`Unknown executable: ${executable}`);
}
return result;
}),
dispose: sinon.stub(),
};
}
```

### 2. Create Test Fixture Data

Add `src/test/fixtures/nativeFinderData.ts` with predefined environment data:

```typescript
import { NativeEnvInfo, NativePythonEnvironmentKind } from '../../managers/common/nativePythonFinder';

export const MOCK_CONDA_ENVS: NativeEnvInfo[] = [
{
displayName: 'base',
name: 'base',
executable: '/opt/conda/bin/python',
kind: NativePythonEnvironmentKind.conda,
version: '3.11.0',
prefix: '/opt/conda',
manager: { tool: 'conda', executable: '/opt/conda/bin/conda', version: '23.5.0' },
},
{
displayName: 'myenv',
name: 'myenv',
executable: '/opt/conda/envs/myenv/bin/python',
kind: NativePythonEnvironmentKind.conda,
version: '3.10.0',
prefix: '/opt/conda/envs/myenv',
manager: { tool: 'conda', executable: '/opt/conda/bin/conda', version: '23.5.0' },
},
];

export const MOCK_VENV_ENVS: NativeEnvInfo[] = [
{
displayName: '.venv',
name: '.venv',
executable: '/workspace/.venv/bin/python',
kind: NativePythonEnvironmentKind.venv,
version: '3.12.0',
prefix: '/workspace/.venv',
},
];

// ... similar for pyenv, pipenv, poetry
```

### 3. Create Mock PythonEnvironmentApi

The managers also depend on `PythonEnvironmentApi`. Add `src/test/mocks/mockPythonApi.ts`:

```typescript
export function createMockPythonEnvironmentApi(): Partial {
return {
getEnvironmentManager: sinon.stub(),
getPackageManager: sinon.stub(),
registerEnvironmentManager: sinon.stub().returns({ dispose: sinon.stub() }),
registerPackageManager: sinon.stub().returns({ dispose: sinon.stub() }),
// ... other minimal stubs
};
}
```

### 4. Simplify Manager Construction

Consider adding a factory or builder pattern for managers to make dependency injection clearer:

```typescript
// In the manager files, add a factory function for testing
export function createCondaEnvManager(
nativeFinder: NativePythonFinder,
api: PythonEnvironmentApi,
log: LogOutputChannel,
): CondaEnvManager {
return new CondaEnvManager(nativeFinder, api, log);
}
```

### 5. Example Test Structure

```typescript
// src/test/managers/conda/condaEnvManager.unit.test.ts
import assert from 'node:assert';
import * as sinon from 'sinon';
import { createMockNativePythonFinder } from '../../mocks/mockNativePythonFinder';
import { createMockPythonEnvironmentApi } from '../../mocks/mockPythonApi';
import { createMockLogOutputChannel } from '../../mocks/helper';
import { MOCK_CONDA_ENVS } from '../../fixtures/nativeFinderData';
import { CondaEnvManager } from '../../../managers/conda/condaEnvManager';

suite('CondaEnvManager', () => {
let manager: CondaEnvManager;
let mockFinder: NativePythonFinder;

setup(() => {
mockFinder = createMockNativePythonFinder({
environments: MOCK_CONDA_ENVS,
});
const mockApi = createMockPythonEnvironmentApi();
const mockLog = createMockLogOutputChannel();

manager = new CondaEnvManager(mockFinder, mockApi as PythonEnvironmentApi, mockLog);
});

teardown(() => {
sinon.restore();
manager.dispose();
});

test('getEnvironments returns all conda environments', async () => {
await manager.initialize();
const envs = await manager.getEnvironments('all');

assert.strictEqual(envs.length, 2);
// ... assertions
});
});
```

## Acceptance Criteria

- [ ] Create `src/test/mocks/mockNativePythonFinder.ts` with full interface implementation
- [ ] Create `src/test/fixtures/nativeFinderData.ts` with realistic test data
- [ ] Create `src/test/mocks/mockPythonApi.ts` for API dependency
- [ ] Add at least basic unit tests for each manager:
- [ ] `CondaEnvManager`
- [ ] `VenvManager`
- [ ] `SysPythonManager`
- [ ] `PyenvManager`
- [ ] `PipenvManager`
- [ ] `PoetryManager`
- [ ] Document the testing patterns in the testing workflow instructions

## Additional Simplifications

1. **Abstract child process spawning**: The `*Utils.ts` files (e.g., `condaUtils.ts`) spawn child processes. These could be abstracted through wrapper functions in `common/childProcess.apis.ts` for easier mocking.

2. **File system operations**: Use the existing `workspace.fs.apis` wrappers consistently across managers for easier stubbing.

3. **Settings access**: Ensure all settings are accessed through `workspace.apis` wrappers rather than direct `vscode.workspace.getConfiguration()` calls.

## Related Files

- `src/managers/common/nativePythonFinder.ts` - The interface to mock
- `src/test/mocks/helper.ts` - Existing mock helpers
- `src/test/features/interpreterSelection.unit.test.ts` - Example of partial NativePythonFinder mocking
- `src/managers/*/` - All manager implementations needing tests

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.