Skip to content

Commit 3bb9893

Browse files
authored
Handle venv creation when running in no-workspace case (#145)
Fixes #140
1 parent 2c9cc77 commit 3bb9893

File tree

4 files changed

+215
-48
lines changed

4 files changed

+215
-48
lines changed

src/common/pickers/projects.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ export async function pickProjectMany(
4949
}
5050
} else if (projects.length === 1) {
5151
return [...projects];
52+
} else if (projects.length === 0) {
53+
return [];
5254
}
5355
return undefined;
5456
}

src/features/envCommands.ts

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -111,44 +111,47 @@ export async function createAnyEnvironmentCommand(
111111
options?: any,
112112
): Promise<PythonEnvironment | undefined> {
113113
const select = options?.selectEnvironment;
114-
const projects = await pickProjectMany(pm.getProjects());
115-
if (projects && projects.length > 0) {
116-
const defaultManagers: InternalEnvironmentManager[] = [];
117-
118-
projects.forEach((p) => {
119-
const manager = em.getEnvironmentManager(p.uri);
120-
if (manager && manager.supportsCreate && !defaultManagers.includes(manager)) {
121-
defaultManagers.push(manager);
122-
}
123-
});
124-
125-
const managerId = await pickEnvironmentManager(
126-
em.managers.filter((m) => m.supportsCreate),
127-
defaultManagers,
128-
);
129-
130-
const manager = em.managers.find((m) => m.id === managerId);
131-
if (manager) {
132-
const env = await manager.create(projects.map((p) => p.uri));
133-
if (select) {
134-
await em.setEnvironments(
135-
projects.map((p) => p.uri),
136-
env,
137-
);
138-
}
139-
return env;
140-
}
141-
} else if (projects && projects.length === 0) {
114+
const projects = pm.getProjects();
115+
if (projects.length === 0) {
142116
const managerId = await pickEnvironmentManager(em.managers.filter((m) => m.supportsCreate));
143-
144117
const manager = em.managers.find((m) => m.id === managerId);
145118
if (manager) {
146119
const env = await manager.create('global');
147-
if (select) {
120+
if (select && env) {
148121
await manager.set(undefined, env);
149122
}
150123
return env;
151124
}
125+
} else if (projects.length > 0) {
126+
const selected = await pickProjectMany(projects);
127+
128+
if (selected && selected.length > 0) {
129+
const defaultManagers: InternalEnvironmentManager[] = [];
130+
131+
selected.forEach((p) => {
132+
const manager = em.getEnvironmentManager(p.uri);
133+
if (manager && manager.supportsCreate && !defaultManagers.includes(manager)) {
134+
defaultManagers.push(manager);
135+
}
136+
});
137+
138+
const managerId = await pickEnvironmentManager(
139+
em.managers.filter((m) => m.supportsCreate),
140+
defaultManagers,
141+
);
142+
143+
const manager = em.managers.find((m) => m.id === managerId);
144+
if (manager) {
145+
const env = await manager.create(selected.map((p) => p.uri));
146+
if (select && env) {
147+
await em.setEnvironments(
148+
selected.map((p) => p.uri),
149+
env,
150+
);
151+
}
152+
return env;
153+
}
154+
}
152155
}
153156
}
154157

src/test/copilotTools.unit.test.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ suite('GetPackagesTool Tests', () => {
1818
mockApi = typeMoq.Mock.ofType<PythonProjectEnvironmentApi & PythonPackageGetterApi>();
1919
mockEnvironment = typeMoq.Mock.ofType<PythonEnvironment>();
2020

21+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
22+
mockEnvironment.setup((x: any) => x.then).returns(() => undefined);
23+
2124
// refresh will always return a resolved promise
2225
mockApi.setup((x) => x.refreshPackages(typeMoq.It.isAny())).returns(() => Promise.resolve());
2326

@@ -30,9 +33,6 @@ suite('GetPackagesTool Tests', () => {
3033
});
3134

3235
test('should throw error if filePath is undefined', async () => {
33-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
34-
mockEnvironment.setup((x: any) => x.then).returns(() => undefined);
35-
3636
const testFile: IGetActiveFile = {
3737
filePath: '',
3838
};
@@ -61,9 +61,6 @@ suite('GetPackagesTool Tests', () => {
6161
});
6262

6363
test('should throw error for notebook cells', async () => {
64-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
65-
mockEnvironment.setup((x: any) => x.then).returns(() => undefined);
66-
6764
const testFile: IGetActiveFile = {
6865
filePath: 'test.ipynb#123',
6966
};
@@ -84,9 +81,6 @@ suite('GetPackagesTool Tests', () => {
8481
filePath: 'test.py',
8582
};
8683

87-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
88-
mockEnvironment.setup((x: any) => x.then).returns(() => undefined);
89-
9084
mockApi
9185
.setup((x) => x.getEnvironment(typeMoq.It.isAny()))
9286
.returns(() => {
@@ -109,9 +103,6 @@ suite('GetPackagesTool Tests', () => {
109103
filePath: 'test.py',
110104
};
111105

112-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
113-
mockEnvironment.setup((x: any) => x.then).returns(() => undefined);
114-
115106
mockApi
116107
.setup((x) => x.getEnvironment(typeMoq.It.isAny()))
117108
.returns(() => {
@@ -152,9 +143,6 @@ suite('GetPackagesTool Tests', () => {
152143
filePath: 'test.py',
153144
};
154145

155-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
156-
mockEnvironment.setup((x: any) => x.then).returns(() => undefined);
157-
158146
mockApi
159147
.setup((x) => x.getEnvironment(typeMoq.It.isAny()))
160148
.returns(() => {
@@ -197,9 +185,6 @@ suite('GetPackagesTool Tests', () => {
197185
filePath: 'test.py',
198186
};
199187

200-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
201-
mockEnvironment.setup((x: any) => x.then).returns(() => undefined);
202-
203188
mockApi
204189
.setup((x) => x.getEnvironment(typeMoq.It.isAny()))
205190
.returns(async () => {
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import * as assert from 'assert';
2+
import * as typeMoq from 'typemoq';
3+
import * as sinon from 'sinon';
4+
import { EnvironmentManagers, InternalEnvironmentManager, PythonProjectManager } from '../../internal.api';
5+
import * as projectApi from '../../common/pickers/projects';
6+
import * as managerApi from '../../common/pickers/managers';
7+
import { PythonEnvironment, PythonProject } from '../../api';
8+
import { createAnyEnvironmentCommand } from '../../features/envCommands';
9+
import { Uri } from 'vscode';
10+
11+
suite('Create Any Environment Command Tests', () => {
12+
let em: typeMoq.IMock<EnvironmentManagers>;
13+
let pm: typeMoq.IMock<PythonProjectManager>;
14+
let manager: typeMoq.IMock<InternalEnvironmentManager>;
15+
let env: typeMoq.IMock<PythonEnvironment>;
16+
let pickProjectManyStub: sinon.SinonStub;
17+
let pickEnvironmentManagerStub: sinon.SinonStub;
18+
let project: PythonProject = {
19+
uri: Uri.file('/some/test/workspace/folder'),
20+
name: 'test-folder',
21+
};
22+
let project2: PythonProject = {
23+
uri: Uri.file('/some/test/workspace/folder2'),
24+
name: 'test-folder2',
25+
};
26+
let project3: PythonProject = {
27+
uri: Uri.file('/some/test/workspace/folder3'),
28+
name: 'test-folder3',
29+
};
30+
31+
setup(() => {
32+
manager = typeMoq.Mock.ofType<InternalEnvironmentManager>();
33+
manager.setup((m) => m.id).returns(() => 'test');
34+
manager.setup((m) => m.displayName).returns(() => 'Test Manager');
35+
manager.setup((m) => m.description).returns(() => 'Test Manager Description');
36+
manager.setup((m) => m.supportsCreate).returns(() => true);
37+
38+
env = typeMoq.Mock.ofType<PythonEnvironment>();
39+
env.setup((e) => e.envId).returns(() => ({ id: 'env1', managerId: 'test' }));
40+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
41+
env.setup((e: any) => e.then).returns(() => undefined);
42+
43+
em = typeMoq.Mock.ofType<EnvironmentManagers>();
44+
em.setup((e) => e.managers).returns(() => [manager.object]);
45+
em.setup((e) => e.getEnvironmentManager(typeMoq.It.isAnyString())).returns(() => manager.object);
46+
47+
pm = typeMoq.Mock.ofType<PythonProjectManager>();
48+
49+
pickEnvironmentManagerStub = sinon.stub(managerApi, 'pickEnvironmentManager');
50+
pickProjectManyStub = sinon.stub(projectApi, 'pickProjectMany');
51+
});
52+
53+
teardown(() => {
54+
sinon.restore();
55+
});
56+
57+
test('Create global venv (no-workspace): no-select', async () => {
58+
pm.setup((p) => p.getProjects()).returns(() => []);
59+
manager
60+
.setup((m) => m.create('global'))
61+
.returns(() => Promise.resolve(env.object))
62+
.verifiable(typeMoq.Times.once());
63+
64+
manager.setup((m) => m.set(typeMoq.It.isAny(), typeMoq.It.isAny())).verifiable(typeMoq.Times.never());
65+
66+
pickEnvironmentManagerStub.resolves(manager.object.id);
67+
pickProjectManyStub.resolves([]);
68+
69+
const result = await createAnyEnvironmentCommand(em.object, pm.object, { selectEnvironment: false });
70+
// Add assertions to verify the result
71+
assert.strictEqual(result, env.object, 'Expected the created environment to match the mocked environment.');
72+
manager.verifyAll();
73+
});
74+
75+
test('Create global venv (no-workspace): select', async () => {
76+
pm.setup((p) => p.getProjects()).returns(() => []);
77+
manager
78+
.setup((m) => m.create('global'))
79+
.returns(() => Promise.resolve(env.object))
80+
.verifiable(typeMoq.Times.once());
81+
82+
manager.setup((m) => m.set(undefined, env.object)).verifiable(typeMoq.Times.once());
83+
84+
pickEnvironmentManagerStub.resolves(manager.object.id);
85+
pickProjectManyStub.resolves([]);
86+
87+
const result = await createAnyEnvironmentCommand(em.object, pm.object, { selectEnvironment: true });
88+
// Add assertions to verify the result
89+
assert.strictEqual(result, env.object, 'Expected the created environment to match the mocked environment.');
90+
manager.verifyAll();
91+
});
92+
93+
test('Create workspace venv: no-select', async () => {
94+
pm.setup((p) => p.getProjects()).returns(() => [project]);
95+
manager
96+
.setup((m) => m.create([project.uri]))
97+
.returns(() => Promise.resolve(env.object))
98+
.verifiable(typeMoq.Times.once());
99+
100+
manager.setup((m) => m.set(typeMoq.It.isAny(), typeMoq.It.isAny())).verifiable(typeMoq.Times.never());
101+
em.setup((e) => e.setEnvironments(typeMoq.It.isAny(), typeMoq.It.isAny())).verifiable(typeMoq.Times.never());
102+
103+
pickEnvironmentManagerStub.resolves(manager.object.id);
104+
pickProjectManyStub.resolves([project]);
105+
106+
const result = await createAnyEnvironmentCommand(em.object, pm.object, { selectEnvironment: false });
107+
108+
assert.strictEqual(result, env.object, 'Expected the created environment to match the mocked environment.');
109+
manager.verifyAll();
110+
em.verifyAll();
111+
});
112+
113+
test('Create workspace venv: select', async () => {
114+
pm.setup((p) => p.getProjects()).returns(() => [project]);
115+
manager
116+
.setup((m) => m.create([project.uri]))
117+
.returns(() => Promise.resolve(env.object))
118+
.verifiable(typeMoq.Times.once());
119+
120+
// This is a case where env managers handler does this in batch to avoid writing to files for each case
121+
manager.setup((m) => m.set(typeMoq.It.isAny(), typeMoq.It.isAny())).verifiable(typeMoq.Times.never());
122+
em.setup((e) => e.setEnvironments([project.uri], env.object)).verifiable(typeMoq.Times.once());
123+
124+
pickEnvironmentManagerStub.resolves(manager.object.id);
125+
pickProjectManyStub.resolves([project]);
126+
127+
const result = await createAnyEnvironmentCommand(em.object, pm.object, { selectEnvironment: true });
128+
129+
assert.strictEqual(result, env.object, 'Expected the created environment to match the mocked environment.');
130+
manager.verifyAll();
131+
em.verifyAll();
132+
});
133+
134+
test('Create multi-workspace venv: select all', async () => {
135+
pm.setup((p) => p.getProjects()).returns(() => [project, project2, project3]);
136+
manager
137+
.setup((m) => m.create([project.uri, project2.uri, project3.uri]))
138+
.returns(() => Promise.resolve(env.object))
139+
.verifiable(typeMoq.Times.once());
140+
141+
// This is a case where env managers handler does this in batch to avoid writing to files for each case
142+
manager.setup((m) => m.set(typeMoq.It.isAny(), typeMoq.It.isAny())).verifiable(typeMoq.Times.never());
143+
em.setup((e) => e.setEnvironments([project.uri, project2.uri, project3.uri], env.object)).verifiable(
144+
typeMoq.Times.once(),
145+
);
146+
147+
pickEnvironmentManagerStub.resolves(manager.object.id);
148+
pickProjectManyStub.resolves([project, project2, project3]);
149+
150+
const result = await createAnyEnvironmentCommand(em.object, pm.object, { selectEnvironment: true });
151+
152+
assert.strictEqual(result, env.object, 'Expected the created environment to match the mocked environment.');
153+
manager.verifyAll();
154+
em.verifyAll();
155+
});
156+
157+
test('Create multi-workspace venv: select some', async () => {
158+
pm.setup((p) => p.getProjects()).returns(() => [project, project2, project3]);
159+
manager
160+
.setup((m) => m.create([project.uri, project3.uri]))
161+
.returns(() => Promise.resolve(env.object))
162+
.verifiable(typeMoq.Times.once());
163+
164+
// This is a case where env managers handler does this in batch to avoid writing to files for each case
165+
manager.setup((m) => m.set(typeMoq.It.isAny(), typeMoq.It.isAny())).verifiable(typeMoq.Times.never());
166+
em.setup((e) => e.setEnvironments([project.uri, project3.uri], env.object)).verifiable(typeMoq.Times.once());
167+
168+
pickEnvironmentManagerStub.resolves(manager.object.id);
169+
pickProjectManyStub.resolves([project, project3]);
170+
171+
const result = await createAnyEnvironmentCommand(em.object, pm.object, { selectEnvironment: true });
172+
173+
assert.strictEqual(result, env.object, 'Expected the created environment to match the mocked environment.');
174+
manager.verifyAll();
175+
em.verifyAll();
176+
});
177+
});

0 commit comments

Comments
 (0)