Skip to content

Commit ddf3b3f

Browse files
author
Kartik Raj
authored
Do not display environments in the list for which running python binary fails (#15449)
* Do not display environments in the list for which running python binary fails * Code reviews
1 parent ee27e8f commit ddf3b3f

File tree

5 files changed

+88
-14
lines changed

5 files changed

+88
-14
lines changed

src/client/pythonEnvironments/base/locator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ export type PythonEnvUpdatedEvent = {
2222
old?: PythonEnvInfo;
2323
/**
2424
* The env info that replaces the old info.
25+
* Update is sent as `undefined` if we find out that the environment is no longer valid.
2526
*/
26-
update: PythonEnvInfo;
27+
update: PythonEnvInfo | undefined;
2728
};
2829

2930
/**

src/client/pythonEnvironments/base/locatorUtils.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
import { Uri } from 'vscode';
5+
import { traceVerbose } from '../../common/logger';
56
import { createDeferred } from '../../common/utils/async';
67
import { getURIFilter } from '../../common/utils/misc';
78
import { IDisposable } from '../../common/utils/resourceLifecycle';
@@ -75,7 +76,7 @@ function getSearchLocationFilters(query: PythonLocatorQuery): ((u: Uri) => boole
7576
* This includes applying any received updates.
7677
*/
7778
export async function getEnvs(iterator: IPythonEnvsIterator): Promise<PythonEnvInfo[]> {
78-
const envs: PythonEnvInfo[] = [];
79+
const envs: (PythonEnvInfo | undefined)[] = [];
7980

8081
const updatesDone = createDeferred<void>();
8182
if (iterator.onUpdated === undefined) {
@@ -87,6 +88,12 @@ export async function getEnvs(iterator: IPythonEnvsIterator): Promise<PythonEnvI
8788
listener.dispose();
8889
} else {
8990
const { index, update } = event;
91+
if (envs[index] === undefined) {
92+
const json = JSON.stringify(update);
93+
traceVerbose(
94+
`Updates sent for an env which was classified as invalid earlier, currently not expected, ${json}`,
95+
);
96+
}
9097
// We don't worry about if envs[index] is set already.
9198
envs[index] = update;
9299
}
@@ -103,7 +110,8 @@ export async function getEnvs(iterator: IPythonEnvsIterator): Promise<PythonEnvI
103110
}
104111
await updatesDone.promise;
105112

106-
return envs;
113+
// Do not return invalid environments
114+
return envs.filter((e) => e !== undefined).map((e) => e!);
107115
}
108116

109117
/**
@@ -175,7 +183,7 @@ export async function resolveEnvFromIterator(
175183
listener = iterator.onUpdated((event: PythonEnvUpdatedEvent | null) => {
176184
if (event === null) {
177185
done.resolve();
178-
} else if (matchEnv(event.update)) {
186+
} else if (!event.update || matchEnv(event.update)) {
179187
resolved = event.update;
180188
}
181189
});

src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ async function* iterEnvsIterator(
5656
state.done = true;
5757
checkIfFinishedAndNotify(state, didUpdate);
5858
listener.dispose();
59+
} else if (event.update === undefined) {
60+
throw new Error(
61+
'Unsupported behavior: `undefined` environment updates are not supported from downstream locators in reducer',
62+
);
5963
} else if (seen[event.index] !== undefined) {
6064
state.pending += 1;
6165
resolveDifferencesInBackground(event.index, event.update, state, didUpdate, seen).ignoreErrors();

src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ export class PythonEnvsResolver implements ILocator {
3030
if (!environment) {
3131
return undefined;
3232
}
33-
const interpreterInfo = await this.environmentInfoService.getEnvironmentInfo(environment.executable.filename);
34-
if (!interpreterInfo) {
33+
const info = await this.environmentInfoService.getEnvironmentInfo(environment.executable.filename);
34+
if (!info) {
3535
return undefined;
3636
}
37-
return getResolvedEnv(interpreterInfo, environment);
37+
return getResolvedEnv(info, environment);
3838
}
3939

4040
public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator {
@@ -61,6 +61,10 @@ export class PythonEnvsResolver implements ILocator {
6161
state.done = true;
6262
checkIfFinishedAndNotify(state, didUpdate);
6363
listener.dispose();
64+
} else if (event.update === undefined) {
65+
throw new Error(
66+
'Unsupported behavior: `undefined` environment updates are not supported from downstream locators in resolver',
67+
);
6468
} else if (seen[event.index] !== undefined) {
6569
seen[event.index] = event.update;
6670
this.resolveInBackground(event.index, state, didUpdate, seen).ignoreErrors();
@@ -97,14 +101,15 @@ export class PythonEnvsResolver implements ILocator {
97101
state.pending += 1;
98102
// It's essential we increment the pending call count before any asynchronus calls in this method.
99103
// We want this to be run even when `resolveInBackground` is called in background.
100-
const interpreterInfo = await this.environmentInfoService.getEnvironmentInfo(
101-
seen[envIndex].executable.filename,
102-
);
103-
if (interpreterInfo) {
104-
const resolvedEnv = getResolvedEnv(interpreterInfo, seen[envIndex]);
105-
const old = seen[envIndex];
104+
const info = await this.environmentInfoService.getEnvironmentInfo(seen[envIndex].executable.filename);
105+
const old = seen[envIndex];
106+
if (info) {
107+
const resolvedEnv = getResolvedEnv(info, seen[envIndex]);
106108
seen[envIndex] = resolvedEnv;
107109
didUpdate.fire({ old, index: envIndex, update: resolvedEnv });
110+
} else {
111+
// Send update that the environment is not valid.
112+
didUpdate.fire({ old, index: envIndex, update: undefined });
108113
}
109114
state.pending -= 1;
110115
checkIfFinishedAndNotify(state, didUpdate);

src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { PythonEnvInfo, PythonEnvKind } from '../../../../../client/pythonEnviro
1414
import { parseVersion } from '../../../../../client/pythonEnvironments/base/info/pythonVersion';
1515
import { PythonEnvUpdatedEvent } from '../../../../../client/pythonEnvironments/base/locator';
1616
import { PythonEnvsResolver } from '../../../../../client/pythonEnvironments/base/locators/composite/environmentsResolver';
17+
import { getEnvs as getEnvsWithUpdates } from '../../../../../client/pythonEnvironments/base/locatorUtils';
1718
import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher';
1819
import * as ExternalDep from '../../../../../client/pythonEnvironments/common/externalDependencies';
1920
import { EnvironmentInfoService } from '../../../../../client/pythonEnvironments/info/environmentInfoService';
@@ -113,6 +114,30 @@ suite('Python envs locator - Environments Resolver', () => {
113114
assert.deepEqual(onUpdatedEvents, expectedUpdates);
114115
});
115116

117+
test('If fetching interpreter info fails, it is not reported in the final list of envs', async () => {
118+
// Arrange
119+
stubShellExec.returns(
120+
new Promise<ExecutionResult<string>>((resolve) => {
121+
resolve({
122+
stderr: 'Kaboom',
123+
stdout: '',
124+
});
125+
}),
126+
);
127+
const env1 = createNamedEnv('env1', '3.5.12b1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec1'));
128+
const env2 = createNamedEnv('env2', '3.8.1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2'));
129+
const environmentsToBeIterated = [env1, env2];
130+
const parentLocator = new SimpleLocator(environmentsToBeIterated);
131+
const resolver = new PythonEnvsResolver(parentLocator, envInfoService, () => true);
132+
133+
// Act
134+
const iterator = resolver.iterEnvs();
135+
const envs = await getEnvsWithUpdates(iterator);
136+
137+
// Assert
138+
assert.deepEqual(envs, []);
139+
});
140+
116141
test('Updates to environments from the incoming iterator are sent correctly followed by the null event', async () => {
117142
// Arrange
118143
const env = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec'));
@@ -254,7 +279,7 @@ suite('Python envs locator - Environments Resolver', () => {
254279
assert.deepEqual(expected, createExpectedEnvInfo(resolvedEnvReturnedByReducer));
255280
});
256281

257-
test('If the parent locator resolves environment, but fetching interpreter info returns undefined, return undefined', async () => {
282+
test('If the parent locator resolves environment, but running interpreter info throws error, return undefined', async () => {
258283
stubShellExec.returns(
259284
new Promise<ExecutionResult<string>>((_resolve, reject) => {
260285
reject();
@@ -282,6 +307,37 @@ suite('Python envs locator - Environments Resolver', () => {
282307
assert.deepEqual(expected, undefined);
283308
});
284309

310+
test('If fetching interpreter info fails with stderr, return undefined', async () => {
311+
stubShellExec.returns(
312+
new Promise<ExecutionResult<string>>((resolve) => {
313+
resolve({
314+
stderr: 'Kaboom',
315+
stdout: '',
316+
});
317+
}),
318+
);
319+
const env = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec'));
320+
const resolvedEnvReturnedByReducer = createNamedEnv(
321+
'env1',
322+
'3.8.1',
323+
PythonEnvKind.Conda,
324+
'resolved/path/to/exec',
325+
);
326+
const parentLocator = new SimpleLocator([], {
327+
resolve: async (e: PythonEnvInfo) => {
328+
if (e === env) {
329+
return resolvedEnvReturnedByReducer;
330+
}
331+
throw new Error('Incorrect environment sent to the resolver');
332+
},
333+
});
334+
const resolver = new PythonEnvsResolver(parentLocator, envInfoService, () => true);
335+
336+
const expected = await resolver.resolveEnv(env);
337+
338+
assert.deepEqual(expected, undefined);
339+
});
340+
285341
test("If the parent locator isn't able to resolve environment, return undefined", async () => {
286342
const env = createNamedEnv('env', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec'));
287343
const parentLocator = new SimpleLocator([], {

0 commit comments

Comments
 (0)