Skip to content

Commit 06d3fb0

Browse files
author
Kartik Raj
committed
Modify resolveEnv()
1 parent 471fa69 commit 06d3fb0

File tree

2 files changed

+126
-52
lines changed

2 files changed

+126
-52
lines changed

src/client/pythonEnvironments/collection/environmentsResolver.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import { Event, EventEmitter } from 'vscode';
66
import { traceVerbose } from '../../common/logger';
77
import { areSameEnvironment, PythonEnvInfo } from '../base/info';
88
import { InterpreterInformation } from '../base/info/interpreter';
9-
import {
10-
ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, QueryForEvent,
11-
} from '../base/locator';
9+
import { ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, QueryForEvent } from '../base/locator';
1210
import { PythonEnvsChangedEvent } from '../base/watcher';
1311
import { IEnvironmentInfoService } from '../info/environmentInfoService';
1412

@@ -19,11 +17,19 @@ export class PythonEnvsResolver implements ILocator {
1917

2018
constructor(
2119
private readonly pythonEnvsReducer: ILocator,
22-
private readonly environmentInfoService: IEnvironmentInfoService,
20+
private readonly environmentInfoService: IEnvironmentInfoService
2321
) {}
2422

25-
public resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
26-
return this.pythonEnvsReducer.resolveEnv(env);
23+
public async resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
24+
const environment = await this.pythonEnvsReducer.resolveEnv(env);
25+
if (!environment) {
26+
return undefined;
27+
}
28+
const interpreterInfo = await this.environmentInfoService.getEnvironmentInfo(environment.executable.filename);
29+
if (!interpreterInfo) {
30+
return undefined;
31+
}
32+
return getResolvedEnv(interpreterInfo, environment);
2733
}
2834

2935
public iterEnvs(query?: QueryForEvent<PythonEnvsChangedEvent>): IPythonEnvsIterator {
@@ -34,13 +40,13 @@ export class PythonEnvsResolver implements ILocator {
3440
return iterator;
3541
}
3642

37-
private async* iterEnvsIterator(
43+
private async *iterEnvsIterator(
3844
iterator: IPythonEnvsIterator,
39-
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>,
45+
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>
4046
): AsyncIterator<PythonEnvInfo, void> {
4147
const state = {
4248
done: false,
43-
pending: 0,
49+
pending: 0
4450
};
4551
const seen: PythonEnvInfo[] = [];
4652

@@ -84,10 +90,10 @@ export class PythonEnvsResolver implements ILocator {
8490
envIndex: number,
8591
state: { done: boolean; pending: number },
8692
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>,
87-
seen: PythonEnvInfo[],
93+
seen: PythonEnvInfo[]
8894
) {
8995
const interpreterInfo = await this.environmentInfoService.getEnvironmentInfo(
90-
seen[envIndex].executable.filename,
96+
seen[envIndex].executable.filename
9197
);
9298
if (interpreterInfo && seen[envIndex]) {
9399
const resolvedEnv = getResolvedEnv(interpreterInfo, seen[envIndex]);
@@ -106,7 +112,7 @@ export class PythonEnvsResolver implements ILocator {
106112
*/
107113
function checkIfFinishedAndNotify(
108114
state: { done: boolean; pending: number },
109-
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>,
115+
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>
110116
) {
111117
if (state.done && state.pending === 0) {
112118
didUpdate.fire(null);

src/test/pythonEnvironments/collection/environmentsResolver.unit.test.ts

Lines changed: 108 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,28 @@ import { PythonEnvUpdatedEvent } from '../../../client/pythonEnvironments/base/l
1414
import { PythonEnvsChangedEvent } from '../../../client/pythonEnvironments/base/watcher';
1515
import { PythonEnvsResolver } from '../../../client/pythonEnvironments/collection/environmentsResolver';
1616
import * as ExternalDep from '../../../client/pythonEnvironments/common/externalDependencies';
17-
import {
18-
EnvironmentInfoService,
19-
IEnvironmentInfoService,
20-
} from '../../../client/pythonEnvironments/info/environmentInfoService';
17+
import { EnvironmentInfoService } from '../../../client/pythonEnvironments/info/environmentInfoService';
2118
import { sleep } from '../../core';
2219
import { createEnv, getEnvs, SimpleLocator } from '../base/common';
2320

2421
suite('Environments Resolver', () => {
22+
/**
23+
* Returns the expected environment to be returned by Environment info service
24+
*/
25+
function createExpectedEnvInfo(env: PythonEnvInfo): PythonEnvInfo {
26+
const updatedEnv = cloneDeep(env);
27+
updatedEnv.version = {
28+
...parseVersion('3.8.3-final'),
29+
sysVersion: '3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)]',
30+
};
31+
updatedEnv.executable.filename = env.executable.filename;
32+
updatedEnv.executable.sysPrefix = 'path';
33+
updatedEnv.arch = Architecture.x64;
34+
return updatedEnv;
35+
}
2536
suite('iterEnvs()', () => {
2637
let stubShellExec: sinon.SinonStub;
27-
let envService: IEnvironmentInfoService;
28-
2938
setup(() => {
30-
envService = new EnvironmentInfoService();
3139
stubShellExec = ImportMock.mockFunction(
3240
ExternalDep,
3341
'shellExecute',
@@ -44,23 +52,14 @@ suite('Environments Resolver', () => {
4452
stubShellExec.restore();
4553
});
4654

47-
function createExpectedEnvInfo(env: PythonEnvInfo): PythonEnvInfo {
48-
const updatedEnv = cloneDeep(env);
49-
updatedEnv.version = { ...parseVersion('3.8.3-final'), sysVersion: '3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)]' };
50-
updatedEnv.executable.filename = env.executable.filename;
51-
updatedEnv.executable.sysPrefix = 'path';
52-
updatedEnv.arch = Architecture.x64;
53-
return updatedEnv;
54-
}
55-
5655
test('Iterator only yields as-is', async () => {
5756
const env1 = createEnv('env1', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1'));
5857
const env2 = createEnv('env2', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2'));
5958
const env3 = createEnv('env3', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3'));
6059
const env4 = createEnv('env4', '3.9.0rc2', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2'));
6160
const environmentsToBeIterated = [env1, env2, env3, env4];
62-
const pythonEnvManager = new SimpleLocator(environmentsToBeIterated);
63-
const reducer = new PythonEnvsResolver(pythonEnvManager, envService);
61+
const pythonEnvReducer = new SimpleLocator(environmentsToBeIterated);
62+
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());
6463

6564
const iterator = reducer.iterEnvs();
6665
const envs = await getEnvs(iterator);
@@ -73,9 +72,9 @@ suite('Environments Resolver', () => {
7372
const env1 = createEnv('env1', '3.5.12b1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec1'));
7473
const env2 = createEnv('env2', '3.8.1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2'));
7574
const environmentsToBeIterated = [env1, env2];
76-
const pythonEnvManager = new SimpleLocator(environmentsToBeIterated);
75+
const pythonEnvReducer = new SimpleLocator(environmentsToBeIterated);
7776
const onUpdatedEvents: (PythonEnvUpdatedEvent | null)[] = [];
78-
const reducer = new PythonEnvsResolver(pythonEnvManager, envService);
77+
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());
7978

8079
const iterator = reducer.iterEnvs(); // Act
8180

@@ -108,9 +107,9 @@ suite('Environments Resolver', () => {
108107
const updatedEnv = createEnv('env1', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec'));
109108
const environmentsToBeIterated = [env];
110109
const didUpdate = new EventEmitter<PythonEnvUpdatedEvent | null>();
111-
const pythonEnvManager = new SimpleLocator(environmentsToBeIterated, { onUpdated: didUpdate.event });
110+
const pythonEnvReducer = new SimpleLocator(environmentsToBeIterated, { onUpdated: didUpdate.event });
112111
const onUpdatedEvents: (PythonEnvUpdatedEvent | null)[] = [];
113-
const reducer = new PythonEnvsResolver(pythonEnvManager, envService);
112+
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());
114113

115114
const iterator = reducer.iterEnvs(); // Act
116115

@@ -134,43 +133,112 @@ suite('Environments Resolver', () => {
134133
// Assert
135134
// The updates can be anything, even the number of updates, but they should lead to the same final state
136135
const { length } = onUpdatedEvents;
137-
assert.deepEqual(onUpdatedEvents[length - 2]?.new, createExpectedEnvInfo(updatedEnv), 'The final update to environment is incorrect');
136+
assert.deepEqual(
137+
onUpdatedEvents[length - 2]?.new,
138+
createExpectedEnvInfo(updatedEnv),
139+
'The final update to environment is incorrect',
140+
);
138141
assert.equal(onUpdatedEvents[length - 1], null, 'Last update should be null');
139142
didUpdate.dispose();
140143
});
141144
});
142145

143146
test('onChanged fires iff onChanged from reducer fires', () => {
144-
const pythonEnvManager = new SimpleLocator([]);
147+
const pythonEnvReducer = new SimpleLocator([]);
145148
const event1: PythonEnvsChangedEvent = {};
146149
const event2: PythonEnvsChangedEvent = { kind: PythonEnvKind.Unknown };
147150
const expected = [event1, event2];
148-
const reducer = new PythonEnvsResolver(pythonEnvManager, new EnvironmentInfoService());
151+
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());
149152

150153
const events: PythonEnvsChangedEvent[] = [];
151154
reducer.onChanged((e) => events.push(e));
152155

153-
pythonEnvManager.fire(event1);
154-
pythonEnvManager.fire(event2);
156+
pythonEnvReducer.fire(event1);
157+
pythonEnvReducer.fire(event2);
155158

156159
assert.deepEqual(events, expected);
157160
});
158161

159-
test('Calls reducer to resolves environments', async () => {
160-
const env = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec'));
161-
const resolvedEnv = createEnv('env1', '3.8.1', PythonEnvKind.Conda, 'resolved/path/to/exec');
162-
const pythonEnvManager = new SimpleLocator([], {
163-
resolve: async (e: PythonEnvInfo) => {
164-
if (e === env) {
165-
return resolvedEnv;
166-
}
167-
return undefined;
168-
},
162+
suite('resolveEnv()', () => {
163+
let stubShellExec: sinon.SinonStub;
164+
setup(() => {
165+
stubShellExec = ImportMock.mockFunction(
166+
ExternalDep,
167+
'shellExecute',
168+
new Promise<ExecutionResult<string>>((resolve) => {
169+
resolve({
170+
stdout:
171+
'{"versionInfo": [3, 8, 3, "final", 0], "sysPrefix": "path", "sysVersion": "3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)]", "is64Bit": true}',
172+
});
173+
}),
174+
);
175+
});
176+
177+
teardown(() => {
178+
stubShellExec.restore();
179+
});
180+
181+
test('Calls into reducer to get resolved environment, then calls environnment service to resolve environment further and return it', async () => {
182+
const env = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec'));
183+
const resolvedEnvReturnedByReducer = createEnv(
184+
'env1',
185+
'3.8.1',
186+
PythonEnvKind.Conda,
187+
'resolved/path/to/exec',
188+
);
189+
const pythonEnvReducer = new SimpleLocator([], {
190+
resolve: async (e: PythonEnvInfo) => {
191+
if (e === env) {
192+
return resolvedEnvReturnedByReducer;
193+
}
194+
throw new Error('Incorrect environment sent to the reducer');
195+
},
196+
});
197+
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());
198+
199+
const expected = await reducer.resolveEnv(env);
200+
201+
assert.deepEqual(expected, createExpectedEnvInfo(resolvedEnvReturnedByReducer));
169202
});
170-
const reducer = new PythonEnvsResolver(pythonEnvManager, new EnvironmentInfoService());
171203

172-
const expected = await reducer.resolveEnv(env);
204+
test('If the reducer resolves environment, but fetching interpreter info returns undefined, return undefined', async () => {
205+
stubShellExec.returns(
206+
new Promise<ExecutionResult<string>>((_resolve, reject) => {
207+
reject();
208+
}),
209+
);
210+
const env = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec'));
211+
const resolvedEnvReturnedByReducer = createEnv(
212+
'env1',
213+
'3.8.1',
214+
PythonEnvKind.Conda,
215+
'resolved/path/to/exec',
216+
);
217+
const pythonEnvReducer = new SimpleLocator([], {
218+
resolve: async (e: PythonEnvInfo) => {
219+
if (e === env) {
220+
return resolvedEnvReturnedByReducer;
221+
}
222+
throw new Error('Incorrect environment sent to the reducer');
223+
},
224+
});
225+
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());
226+
227+
const expected = await reducer.resolveEnv(env);
228+
229+
assert.deepEqual(expected, undefined);
230+
});
173231

174-
assert.deepEqual(expected, resolvedEnv);
232+
test("If the reducer isn't able to resolve environment, return undefined", async () => {
233+
const env = createEnv('env', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec'));
234+
const pythonEnvReducer = new SimpleLocator([], {
235+
resolve: async () => undefined,
236+
});
237+
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());
238+
239+
const expected = await reducer.resolveEnv(env);
240+
241+
assert.deepEqual(expected, undefined);
242+
});
175243
});
176244
});

0 commit comments

Comments
 (0)