Skip to content

Commit 0c78021

Browse files
author
Kartik Raj
committed
Modify resolveEnv()
1 parent 471fa69 commit 0c78021

File tree

3 files changed

+121
-77
lines changed

3 files changed

+121
-77
lines changed

src/client/pythonEnvironments/collection/environmentsResolver.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,16 @@ export class PythonEnvsResolver implements ILocator {
2222
private readonly environmentInfoService: IEnvironmentInfoService,
2323
) {}
2424

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

2937
public iterEnvs(query?: QueryForEvent<PythonEnvsChangedEvent>): IPythonEnvsIterator {
@@ -89,7 +97,7 @@ export class PythonEnvsResolver implements ILocator {
8997
const interpreterInfo = await this.environmentInfoService.getEnvironmentInfo(
9098
seen[envIndex].executable.filename,
9199
);
92-
if (interpreterInfo && seen[envIndex]) {
100+
if (interpreterInfo) {
93101
const resolvedEnv = getResolvedEnv(interpreterInfo, seen[envIndex]);
94102
didUpdate.fire({ old: seen[envIndex], new: resolvedEnv });
95103
seen[envIndex] = resolvedEnv;

src/test/pythonEnvironments/base/common.ts

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@
44
import { Event } from 'vscode';
55
import { createDeferred, flattenIterator, iterable, mapToIterator } from '../../../client/common/utils/async';
66
import { Architecture } from '../../../client/common/utils/platform';
7-
import { EMPTY_VERSION, parseBasicVersionInfo } from '../../../client/common/utils/version';
87
import {
98
PythonEnvInfo,
109
PythonEnvKind,
11-
PythonReleaseLevel,
12-
PythonVersion
1310
} from '../../../client/pythonEnvironments/base/info';
11+
import { parseVersion } from '../../../client/pythonEnvironments/base/info/pythonVersion';
1412
import { IPythonEnvsIterator, Locator, PythonEnvUpdatedEvent, PythonLocatorQuery } from '../../../client/pythonEnvironments/base/locator';
1513
import { PythonEnvsChangedEvent } from '../../../client/pythonEnvironments/base/watcher';
1614

@@ -46,36 +44,6 @@ export function createEnv(
4644
};
4745
}
4846

49-
function parseVersion(versionStr: string): PythonVersion {
50-
const parsed = parseBasicVersionInfo<PythonVersion>(versionStr);
51-
if (!parsed) {
52-
if (versionStr === '') {
53-
return EMPTY_VERSION as PythonVersion;
54-
}
55-
throw Error(`invalid version ${versionStr}`);
56-
}
57-
const { version, after } = parsed;
58-
const match = after.match(/^(a|b|rc)(\d+)$/);
59-
if (match) {
60-
const [, levelStr, serialStr ] = match;
61-
let level: PythonReleaseLevel;
62-
if (levelStr === 'a') {
63-
level = PythonReleaseLevel.Alpha;
64-
} else if (levelStr === 'b') {
65-
level = PythonReleaseLevel.Beta;
66-
} else if (levelStr === 'rc') {
67-
level = PythonReleaseLevel.Candidate;
68-
} else {
69-
throw Error('unreachable!');
70-
}
71-
version.release = {
72-
level,
73-
serial: parseInt(serialStr, 10)
74-
};
75-
}
76-
return version;
77-
}
78-
7947
export function createLocatedEnv(
8048
location: string,
8149
versionStr: string,

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

Lines changed: 109 additions & 41 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-
56-
test('Iterator only yields as-is', async () => {
55+
test('Iterator yields environments 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)