Skip to content

Commit f4a6feb

Browse files
authored
Merge branch 'main' into electrical-mongoose
2 parents ce7a272 + 9c4f097 commit f4a6feb

7 files changed

Lines changed: 180 additions & 47 deletions

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "vscode-python-envs",
33
"displayName": "Python Environments",
44
"description": "Provides a unified python environment experience",
5-
"version": "1.30.0",
5+
"version": "1.31.0",
66
"publisher": "ms-python",
77
"preview": true,
88
"engines": {

src/managers/conda/condaEnvManager.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
9595
title: CondaStrings.condaDiscovering,
9696
},
9797
async () => {
98-
this.collection = (await refreshCondaEnvs(false, this.nativeFinder, this.api, this.log, this)) ?? [];
98+
this.collection =
99+
(await refreshCondaEnvs(false, this.nativeFinder, this.api, this.log, this)) ?? [];
99100
await this.loadEnvMap();
100101

101102
this._onDidChangeEnvironments.fire(
@@ -273,7 +274,8 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
273274
resolve: (p) => resolveCondaPath(p, this.nativeFinder, this.api, this.log, this),
274275
startBackgroundInit: () =>
275276
withProgress({ location: ProgressLocation.Window, title: CondaStrings.condaDiscovering }, async () => {
276-
this.collection = (await refreshCondaEnvs(false, this.nativeFinder, this.api, this.log, this)) ?? [];
277+
this.collection =
278+
(await refreshCondaEnvs(false, this.nativeFinder, this.api, this.log, this)) ?? [];
277279
await this.loadEnvMap();
278280
this._onDidChangeEnvironments.fire(
279281
this.collection.map((e) => ({

src/test/managers/conda/condaEnvManager.findEnvironmentByPath.unit.test.ts

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,11 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import assert from 'assert';
33
import * as sinon from 'sinon';
4-
import { Uri } from 'vscode';
54
import { PythonEnvironment, PythonEnvironmentApi } from '../../../api';
65
import { isWindows } from '../../../common/utils/platformUtils';
7-
import { PythonEnvironmentImpl } from '../../../internal.api';
86
import { CondaEnvManager } from '../../../managers/conda/condaEnvManager';
97
import { NativePythonFinder } from '../.././../managers/common/nativePythonFinder';
10-
11-
/**
12-
* Helper to create a minimal PythonEnvironment stub with required fields.
13-
* Only `name`, `environmentPath`, and `version` matter for findEnvironmentByPath.
14-
*/
15-
function makeEnv(name: string, envPath: string, version: string = '3.12.0'): PythonEnvironment {
16-
return new PythonEnvironmentImpl(
17-
{ id: `${name}-test`, managerId: 'ms-python.python:conda' },
18-
{
19-
name,
20-
displayName: `${name} (${version})`,
21-
displayPath: envPath,
22-
version,
23-
environmentPath: Uri.file(envPath),
24-
sysPrefix: envPath,
25-
execInfo: {
26-
run: { executable: 'python' },
27-
},
28-
},
29-
);
30-
}
8+
import { makeMockCondaEnvironment as makeEnv } from '../../mocks/pythonEnvironment';
319

3210
/**
3311
* Creates a CondaEnvManager with a given collection, bypassing initialization.

src/test/managers/conda/condaEnvManager.setEvents.unit.test.ts

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,12 @@
22
import assert from 'assert';
33
import * as sinon from 'sinon';
44
import { Uri } from 'vscode';
5-
import { DidChangeEnvironmentEventArgs, PythonEnvironment, PythonEnvironmentApi, PythonProject } from '../../../api';
5+
import { DidChangeEnvironmentEventArgs, PythonEnvironmentApi, PythonProject } from '../../../api';
66
import { normalizePath } from '../../../common/utils/pathUtils';
7-
import { PythonEnvironmentImpl } from '../../../internal.api';
87
import { CondaEnvManager } from '../../../managers/conda/condaEnvManager';
98
import * as condaUtils from '../../../managers/conda/condaUtils';
109
import { NativePythonFinder } from '../../../managers/common/nativePythonFinder';
11-
12-
function makeEnv(name: string, envPath: string, version: string = '3.12.0'): PythonEnvironment {
13-
return new PythonEnvironmentImpl(
14-
{ id: `${name}-test`, managerId: 'ms-python.python:conda' },
15-
{
16-
name,
17-
displayName: `${name} (${version})`,
18-
displayPath: envPath,
19-
version,
20-
environmentPath: Uri.file(envPath),
21-
sysPrefix: envPath,
22-
execInfo: {
23-
run: { executable: 'python' },
24-
},
25-
},
26-
);
27-
}
10+
import { makeMockCondaEnvironment as makeEnv } from '../../mocks/pythonEnvironment';
2811

2912
function createManager(apiOverrides?: Partial<PythonEnvironmentApi>): CondaEnvManager {
3013
const api = {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import assert from 'assert';
3+
import * as sinon from 'sinon';
4+
import { PythonEnvironmentApi } from '../../../api';
5+
import { CondaEnvManager } from '../../../managers/conda/condaEnvManager';
6+
import * as condaUtils from '../../../managers/conda/condaUtils';
7+
import { NativePythonFinder } from '../../../managers/common/nativePythonFinder';
8+
import { makeMockCondaEnvironment as makeEnv } from '../../mocks/pythonEnvironment';
9+
10+
function createManager(): CondaEnvManager {
11+
const manager = new CondaEnvManager(
12+
{} as NativePythonFinder,
13+
{} as PythonEnvironmentApi,
14+
{ info: sinon.stub(), error: sinon.stub(), warn: sinon.stub() } as any,
15+
);
16+
// Bypass initialization
17+
(manager as any)._initialized = { completed: true, promise: Promise.resolve() };
18+
(manager as any).collection = [];
19+
return manager;
20+
}
21+
22+
suite('CondaEnvManager.set - globalEnv update', () => {
23+
let setCondaForGlobalStub: sinon.SinonStub;
24+
let checkNoPythonStub: sinon.SinonStub;
25+
26+
setup(() => {
27+
setCondaForGlobalStub = sinon.stub(condaUtils, 'setCondaForGlobal').resolves();
28+
checkNoPythonStub = sinon.stub(condaUtils, 'checkForNoPythonCondaEnvironment');
29+
});
30+
31+
teardown(() => {
32+
sinon.restore();
33+
});
34+
35+
test('set(undefined, env) updates globalEnv in memory', async () => {
36+
const manager = createManager();
37+
const oldEnv = makeEnv('base', '/miniconda3', '3.11.0');
38+
const newEnv = makeEnv('myenv', '/miniconda3/envs/myenv', '3.12.0');
39+
(manager as any).globalEnv = oldEnv;
40+
41+
// checkForNoPythonCondaEnvironment returns the env as-is (has Python)
42+
checkNoPythonStub.resolves(newEnv);
43+
44+
await manager.set(undefined, newEnv);
45+
46+
// globalEnv should now be updated in memory
47+
const result = await manager.get(undefined);
48+
assert.strictEqual(result, newEnv, 'get(undefined) should return the newly set environment');
49+
assert.notStrictEqual(result, oldEnv, 'get(undefined) should NOT return the old environment');
50+
});
51+
52+
test('set(undefined, env) persists to disk', async () => {
53+
const manager = createManager();
54+
const newEnv = makeEnv('myenv', '/miniconda3/envs/myenv', '3.12.0');
55+
checkNoPythonStub.resolves(newEnv);
56+
57+
await manager.set(undefined, newEnv);
58+
59+
assert.ok(setCondaForGlobalStub.calledOnce, 'setCondaForGlobal should be called');
60+
assert.strictEqual(
61+
setCondaForGlobalStub.firstCall.args[0],
62+
newEnv.environmentPath.fsPath,
63+
'should persist the correct path',
64+
);
65+
});
66+
67+
test('set(undefined, undefined) clears globalEnv', async () => {
68+
const manager = createManager();
69+
const oldEnv = makeEnv('base', '/miniconda3', '3.11.0');
70+
(manager as any).globalEnv = oldEnv;
71+
72+
await manager.set(undefined, undefined);
73+
74+
const result = await manager.get(undefined);
75+
assert.strictEqual(result, undefined, 'get(undefined) should return undefined after clearing');
76+
});
77+
78+
test('set(undefined, noPythonEnv) where user declines install clears globalEnv', async () => {
79+
const manager = createManager();
80+
const oldEnv = makeEnv('base', '/miniconda3', '3.11.0');
81+
const noPythonEnv = makeEnv('nopy', '/miniconda3/envs/nopy', 'no-python');
82+
(manager as any).globalEnv = oldEnv;
83+
84+
// User declined to install Python
85+
checkNoPythonStub.resolves(undefined);
86+
87+
await manager.set(undefined, noPythonEnv);
88+
89+
const result = await manager.get(undefined);
90+
assert.strictEqual(result, undefined, 'globalEnv should be cleared when checkedEnv is undefined');
91+
});
92+
});
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { Uri } from 'vscode';
5+
import { PythonEnvironment } from '../../api';
6+
import { PythonEnvironmentImpl } from '../../internal.api';
7+
8+
/**
9+
* Options for {@link createMockPythonEnvironment}.
10+
*/
11+
export interface MockPythonEnvironmentOptions {
12+
/** Environment name, e.g. `myenv`. Defaults to `test-env`. */
13+
name?: string;
14+
/** Filesystem path for `environmentPath`, `displayPath`, and `sysPrefix`. */
15+
envPath: string;
16+
/** Version string. Defaults to `3.12.0`. */
17+
version?: string;
18+
/** Manager id. Defaults to `ms-python.python:conda`. */
19+
managerId?: string;
20+
/** Environment id. Defaults to `<name>-test`. */
21+
id?: string;
22+
/** Optional description. */
23+
description?: string;
24+
/** Optional display name. Defaults to `<name> (<version>)`. */
25+
displayName?: string;
26+
/** If true, includes an `activation` entry in `execInfo`. */
27+
hasActivation?: boolean;
28+
}
29+
30+
/**
31+
* Create a minimal {@link PythonEnvironment} for use in unit tests.
32+
*
33+
* Shared across manager and view tests so they agree on the shape of a mock
34+
* environment. Only fields that tests commonly need are populated; extend this
35+
* helper if additional fields become required.
36+
*/
37+
export function createMockPythonEnvironment(options: MockPythonEnvironmentOptions): PythonEnvironment {
38+
const {
39+
name = 'test-env',
40+
envPath,
41+
version = '3.12.0',
42+
managerId = 'ms-python.python:conda',
43+
id = `${name}-test`,
44+
description,
45+
displayName = `${name} (${version})`,
46+
hasActivation = false,
47+
} = options;
48+
49+
return new PythonEnvironmentImpl(
50+
{ id, managerId },
51+
{
52+
name,
53+
displayName,
54+
displayPath: envPath,
55+
version,
56+
description,
57+
environmentPath: Uri.file(envPath),
58+
sysPrefix: envPath,
59+
execInfo: {
60+
run: { executable: 'python' },
61+
...(hasActivation && {
62+
activation: [{ executable: envPath.replace('python', 'activate') }],
63+
}),
64+
},
65+
},
66+
);
67+
}
68+
69+
/**
70+
* Positional shorthand for {@link createMockPythonEnvironment} that always
71+
* creates a conda environment (`managerId` = `ms-python.python:conda`).
72+
* Used by conda manager unit tests. Prefer {@link createMockPythonEnvironment}
73+
* for new tests that need to customize additional fields or target a different
74+
* manager.
75+
*/
76+
export function makeMockCondaEnvironment(name: string, envPath: string, version: string = '3.12.0'): PythonEnvironment {
77+
return createMockPythonEnvironment({ name, envPath, version });
78+
}

0 commit comments

Comments
 (0)