Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 069bced

Browse files
author
Kartik Raj
authored
Ensure command to get executable is run only once per session (microsoft#19606)
1 parent 3c84012 commit 069bced

File tree

4 files changed

+32
-24
lines changed

4 files changed

+32
-24
lines changed

src/client/common/process/pythonEnvironment.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import { buildPythonExecInfo, PythonExecInfo } from '../../pythonEnvironments/ex
88
import { InterpreterInformation } from '../../pythonEnvironments/info';
99
import { getExecutablePath } from '../../pythonEnvironments/info/executable';
1010
import { getInterpreterInfo } from '../../pythonEnvironments/info/interpreter';
11+
import { isTestExecution } from '../constants';
1112
import { IFileSystem } from '../platform/types';
1213
import * as internalPython from './internal/python';
1314
import { ExecutionResult, IProcessService, IPythonEnvironment, ShellOptions, SpawnOptions } from './types';
1415

16+
const cachedExecutablePath: Map<string, Promise<string | undefined>> = new Map<string, Promise<string | undefined>>();
17+
1518
class PythonEnvironment implements IPythonEnvironment {
16-
private cachedExecutablePath: Map<string, Promise<string>> = new Map<string, Promise<string>>();
1719
private cachedInterpreterInformation: InterpreterInformation | undefined | null = null;
1820

1921
constructor(
@@ -45,20 +47,20 @@ class PythonEnvironment implements IPythonEnvironment {
4547
return this.cachedInterpreterInformation;
4648
}
4749

48-
public async getExecutablePath(): Promise<string> {
50+
public async getExecutablePath(): Promise<string | undefined> {
4951
// If we've passed the python file, then return the file.
5052
// This is because on mac if using the interpreter /usr/bin/python2.7 we can get a different value for the path
5153
if (await this.deps.isValidExecutable(this.pythonPath)) {
5254
return this.pythonPath;
5355
}
54-
const result = this.cachedExecutablePath.get(this.pythonPath);
55-
if (result !== undefined) {
56+
const result = cachedExecutablePath.get(this.pythonPath);
57+
if (result !== undefined && !isTestExecution()) {
5658
// Another call for this environment has already been made, return its result
5759
return result;
5860
}
5961
const python = this.getExecutionInfo();
6062
const promise = getExecutablePath(python, this.deps.shellExec);
61-
this.cachedExecutablePath.set(this.pythonPath, promise);
63+
cachedExecutablePath.set(this.pythonPath, promise);
6264
return promise;
6365
}
6466

src/client/common/process/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export const IPythonExecutionService = Symbol('IPythonExecutionService');
8989

9090
export interface IPythonExecutionService {
9191
getInterpreterInformation(): Promise<InterpreterInformation | undefined>;
92-
getExecutablePath(): Promise<string>;
92+
getExecutablePath(): Promise<string | undefined>;
9393
isModuleInstalled(moduleName: string): Promise<boolean>;
9494
getModuleVersion(moduleName: string): Promise<string | undefined>;
9595
getExecutionInfo(pythonArgs?: string[]): PythonExecInfo;
@@ -105,7 +105,7 @@ export interface IPythonExecutionService {
105105
export interface IPythonEnvironment {
106106
getInterpreterInformation(): Promise<InterpreterInformation | undefined>;
107107
getExecutionObservableInfo(pythonArgs?: string[], pythonExecutable?: string): PythonExecInfo;
108-
getExecutablePath(): Promise<string>;
108+
getExecutablePath(): Promise<string | undefined>;
109109
isModuleInstalled(moduleName: string): Promise<boolean>;
110110
getModuleVersion(moduleName: string): Promise<string | undefined>;
111111
getExecutionInfo(pythonArgs?: string[], pythonExecutable?: string): PythonExecInfo;

src/client/pythonEnvironments/info/executable.ts

+20-14
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import { getExecutable } from '../../common/process/internal/python';
55
import { ShellExecFunc } from '../../common/process/types';
6+
import { traceError } from '../../logging';
67
import { copyPythonExecInfo, PythonExecInfo } from '../exec';
78

89
/**
@@ -17,19 +18,24 @@ export async function getExecutablePath(
1718
python: PythonExecInfo,
1819
shellExec: ShellExecFunc,
1920
timeout?: number,
20-
): Promise<string> {
21-
const [args, parse] = getExecutable();
22-
const info = copyPythonExecInfo(python, args);
23-
const argv = [info.command, ...info.args];
24-
// Concat these together to make a set of quoted strings
25-
const quoted = argv.reduce(
26-
(p, c) => (p ? `${p} ${c.toCommandArgumentForPythonExt()}` : `${c.toCommandArgumentForPythonExt()}`),
27-
'',
28-
);
29-
const result = await shellExec(quoted, { timeout: timeout ?? 15000 });
30-
const executable = parse(result.stdout.trim());
31-
if (executable === '') {
32-
throw new Error(`${quoted} resulted in empty stdout`);
21+
): Promise<string | undefined> {
22+
try {
23+
const [args, parse] = getExecutable();
24+
const info = copyPythonExecInfo(python, args);
25+
const argv = [info.command, ...info.args];
26+
// Concat these together to make a set of quoted strings
27+
const quoted = argv.reduce(
28+
(p, c) => (p ? `${p} ${c.toCommandArgumentForPythonExt()}` : `${c.toCommandArgumentForPythonExt()}`),
29+
'',
30+
);
31+
const result = await shellExec(quoted, { timeout: timeout ?? 15000 });
32+
const executable = parse(result.stdout.trim());
33+
if (executable === '') {
34+
throw new Error(`${quoted} resulted in empty stdout`);
35+
}
36+
return executable;
37+
} catch (ex) {
38+
traceError(ex);
39+
return undefined;
3340
}
34-
return executable;
3541
}

src/test/common/process/pythonEnvironment.unit.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -202,17 +202,17 @@ suite('PythonEnvironment', () => {
202202
expect(result).to.equal(executablePath, "getExecutablePath() sbould not return pythonPath if it's not a file");
203203
});
204204

205-
test('getExecutablePath should throw if the result of exec() writes to stderr', async () => {
205+
test('getExecutablePath should return `undefined` if the result of exec() writes to stderr', async () => {
206206
const stderr = 'bar';
207207
fileSystem.setup((f) => f.pathExists(pythonPath)).returns(() => Promise.resolve(false));
208208
processService
209209
.setup((p) => p.shellExec(`${pythonPath} -c "import sys;print(sys.executable)"`, TypeMoq.It.isAny()))
210210
.returns(() => Promise.reject(new StdErrError(stderr)));
211211
const env = createPythonEnv(pythonPath, processService.object, fileSystem.object);
212212

213-
const result = env.getExecutablePath();
213+
const result = await env.getExecutablePath();
214214

215-
await expect(result).to.eventually.be.rejectedWith(stderr);
215+
expect(result).to.be.equal(undefined);
216216
});
217217

218218
test('isModuleInstalled should call processService.exec()', async () => {

0 commit comments

Comments
 (0)