Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/managers/conda/condaEnvManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,12 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
: undefined;

if (scope === undefined) {
const before = this.globalEnv;
this.globalEnv = checkedEnv;
await setCondaForGlobal(checkedEnv?.environmentPath?.fsPath);
if (before?.envId.id !== checkedEnv?.envId.id) {
this._onDidChangeEnvironment.fire({ uri: undefined, old: before, new: checkedEnv });
}
} else if (scope instanceof Uri) {
const folder = this.api.getPythonProject(scope);
const fsPath = folder?.uri?.fsPath ?? scope.fsPath;
Expand All @@ -327,12 +332,16 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
}

const normalizedFsPath = normalizePath(fsPath);
const before = this.fsPathToEnv.get(normalizedFsPath);
if (checkedEnv) {
this.fsPathToEnv.set(normalizedFsPath, checkedEnv);
} else {
this.fsPathToEnv.delete(normalizedFsPath);
}
await setCondaForWorkspace(fsPath, checkedEnv?.environmentPath.fsPath);
if (before?.envId.id !== checkedEnv?.envId.id) {
this._onDidChangeEnvironment.fire({ uri: scope, old: before, new: checkedEnv });
}
}
} else if (Array.isArray(scope) && scope.every((u) => u instanceof Uri)) {
const projects: PythonProject[] = [];
Expand Down
149 changes: 149 additions & 0 deletions src/test/managers/conda/condaEnvManager.setEvents.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import assert from 'assert';
import * as sinon from 'sinon';
import { Uri } from 'vscode';
import { DidChangeEnvironmentEventArgs, PythonEnvironment, PythonEnvironmentApi, PythonProject } from '../../../api';
import { normalizePath } from '../../../common/utils/pathUtils';
import { PythonEnvironmentImpl } from '../../../internal.api';
import { CondaEnvManager } from '../../../managers/conda/condaEnvManager';
import * as condaUtils from '../../../managers/conda/condaUtils';
import { NativePythonFinder } from '../../../managers/common/nativePythonFinder';

function makeEnv(name: string, envPath: string, version: string = '3.12.0'): PythonEnvironment {
return new PythonEnvironmentImpl(
{ id: `${name}-test`, managerId: 'ms-python.python:conda' },
{
name,
displayName: `${name} (${version})`,
displayPath: envPath,
version,
environmentPath: Uri.file(envPath),
sysPrefix: envPath,
execInfo: {
run: { executable: 'python' },
},
},
);
}

function createManager(apiOverrides?: Partial<PythonEnvironmentApi>): CondaEnvManager {
const api = {
getPythonProject: sinon.stub().returns(undefined),
...apiOverrides,
} as any as PythonEnvironmentApi;
const manager = new CondaEnvManager(
{} as NativePythonFinder,
api,
{ info: sinon.stub(), error: sinon.stub(), warn: sinon.stub() } as any,
);
(manager as any)._initialized = { completed: true, promise: Promise.resolve() };
(manager as any).collection = [];
return manager;
}

suite('CondaEnvManager.set - onDidChangeEnvironment event firing', () => {
let checkNoPythonStub: sinon.SinonStub;

setup(() => {
sinon.stub(condaUtils, 'setCondaForGlobal').resolves();
sinon.stub(condaUtils, 'setCondaForWorkspace').resolves();
checkNoPythonStub = sinon.stub(condaUtils, 'checkForNoPythonCondaEnvironment');
});

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

test('set(undefined, env) fires onDidChangeEnvironment for global scope', async () => {
const manager = createManager();
const oldEnv = makeEnv('base', '/miniconda3', '3.11.0');
const newEnv = makeEnv('myenv', '/miniconda3/envs/myenv', '3.12.0');
(manager as any).globalEnv = oldEnv;
checkNoPythonStub.resolves(newEnv);

const events: DidChangeEnvironmentEventArgs[] = [];
manager.onDidChangeEnvironment((e) => events.push(e));

await manager.set(undefined, newEnv);

assert.strictEqual(events.length, 1, 'should fire exactly one event');
assert.strictEqual(events[0].uri, undefined, 'uri should be undefined for global scope');
assert.strictEqual(events[0].old, oldEnv);
assert.strictEqual(events[0].new, newEnv);
});

test('set(undefined, env) does not fire event when env is unchanged', async () => {
const manager = createManager();
const env = makeEnv('base', '/miniconda3', '3.11.0');
(manager as any).globalEnv = env;
checkNoPythonStub.resolves(env);

const events: DidChangeEnvironmentEventArgs[] = [];
manager.onDidChangeEnvironment((e) => events.push(e));

await manager.set(undefined, env);

assert.strictEqual(events.length, 0, 'should not fire event when env is unchanged');
});

test('set(Uri, env) fires onDidChangeEnvironment for single Uri scope', async () => {
const projectUri = Uri.file('/workspace/project');
const project = { uri: projectUri, name: 'project' } as PythonProject;
const manager = createManager({
getPythonProject: sinon.stub().returns(project) as any,
});
const newEnv = makeEnv('myenv', '/miniconda3/envs/myenv', '3.12.0');
checkNoPythonStub.resolves(newEnv);

const events: DidChangeEnvironmentEventArgs[] = [];
manager.onDidChangeEnvironment((e) => events.push(e));

await manager.set(projectUri, newEnv);

assert.strictEqual(events.length, 1, 'should fire exactly one event');
assert.strictEqual(events[0].uri, projectUri);
assert.strictEqual(events[0].old, undefined);
assert.strictEqual(events[0].new, newEnv);
});

test('set(Uri, env) does not fire event when env is unchanged', async () => {
const projectUri = Uri.file('/workspace/project');
const project = { uri: projectUri, name: 'project' } as PythonProject;
const manager = createManager({
getPythonProject: sinon.stub().returns(project) as any,
});
const env = makeEnv('myenv', '/miniconda3/envs/myenv', '3.12.0');
checkNoPythonStub.resolves(env);

// Pre-populate the map with the same env
(manager as any).fsPathToEnv.set(normalizePath(projectUri.fsPath), env);

const events: DidChangeEnvironmentEventArgs[] = [];
manager.onDidChangeEnvironment((e) => events.push(e));

await manager.set(projectUri, env);

assert.strictEqual(events.length, 0, 'should not fire event when env is unchanged');
});

test('set(Uri, undefined) fires event when clearing environment', async () => {
const projectUri = Uri.file('/workspace/project');
const project = { uri: projectUri, name: 'project' } as PythonProject;
const manager = createManager({
getPythonProject: sinon.stub().returns(project) as any,
});
const oldEnv = makeEnv('myenv', '/miniconda3/envs/myenv', '3.12.0');

// Pre-populate the map
(manager as any).fsPathToEnv.set(normalizePath(projectUri.fsPath), oldEnv);

const events: DidChangeEnvironmentEventArgs[] = [];
manager.onDidChangeEnvironment((e) => events.push(e));

await manager.set(projectUri, undefined);

assert.strictEqual(events.length, 1, 'should fire event when clearing');
assert.strictEqual(events[0].old, oldEnv);
assert.strictEqual(events[0].new, undefined);
});
});
Loading