microsoft / microsoft/vscode-python-environments

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

Abierto
#1,159 1 comentario 0 reacciones 1 asignado Ver en GitHub

@karthiknadig ya está trabajando en esto.

Desde el 3/2/2026.

enhancement testing
Lenguaje dominante
TypeScript
Estrellas
138
Forks
62
Merge medio
1 d 4 h
PR fusionados (30 d)
35

Descripción

# 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

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.