Skip to content

Commit 058aa8d

Browse files
authored
Check if interpreter file exists before displaying in a list (#1352)
Fixes #1305
1 parent 7a05496 commit 058aa8d

File tree

3 files changed

+95
-6
lines changed

3 files changed

+95
-6
lines changed

news/2 Fixes/1305.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ensure interpreter file exists on the file system before including into list of interpreters.

src/client/interpreter/locators/services/currentPathService.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,32 @@ import { inject, injectable } from 'inversify';
22
import * as _ from 'lodash';
33
import * as path from 'path';
44
import { Uri } from 'vscode';
5-
import { PythonSettings } from '../../../common/configSettings';
5+
import { IFileSystem } from '../../../common/platform/types';
66
import { IProcessService } from '../../../common/process/types';
7+
import { IConfigurationService } from '../../../common/types';
78
import { IServiceContainer } from '../../../ioc/types';
89
import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts';
910
import { IVirtualEnvironmentManager } from '../../virtualEnvs/types';
1011
import { CacheableLocatorService } from './cacheableLocatorService';
1112

1213
@injectable()
1314
export class CurrentPathService extends CacheableLocatorService {
15+
private readonly fs: IFileSystem;
1416
public constructor(@inject(IVirtualEnvironmentManager) private virtualEnvMgr: IVirtualEnvironmentManager,
1517
@inject(IInterpreterVersionService) private versionProvider: IInterpreterVersionService,
1618
@inject(IProcessService) private processService: IProcessService,
1719
@inject(IServiceContainer) serviceContainer: IServiceContainer) {
1820
super('CurrentPathService', serviceContainer);
21+
this.fs = serviceContainer.get<IFileSystem>(IFileSystem);
1922
}
2023
// tslint:disable-next-line:no-empty
2124
public dispose() { }
2225
protected getInterpretersImplementation(resource?: Uri): Promise<PythonInterpreter[]> {
2326
return this.suggestionsFromKnownPaths();
2427
}
2528
private async suggestionsFromKnownPaths(resource?: Uri) {
26-
const currentPythonInterpreter = this.getInterpreter(PythonSettings.getInstance(resource).pythonPath, '').then(interpreter => [interpreter]);
29+
const configSettings = this.serviceContainer.get<IConfigurationService>(IConfigurationService).getSettings(resource);
30+
const currentPythonInterpreter = this.getInterpreter(configSettings.pythonPath, '').then(interpreter => [interpreter]);
2731
const python = this.getInterpreter('python', '').then(interpreter => [interpreter]);
2832
const python2 = this.getInterpreter('python2', '').then(interpreter => [interpreter]);
2933
const python3 = this.getInterpreter('python3', '').then(interpreter => [interpreter]);
@@ -49,9 +53,15 @@ export class CurrentPathService extends CacheableLocatorService {
4953
});
5054
}
5155
private async getInterpreter(pythonPath: string, defaultValue: string) {
52-
return this.processService.exec(pythonPath, ['-c', 'import sys;print(sys.executable)'], {})
53-
.then(output => output.stdout.trim())
54-
.then(value => value.length === 0 ? defaultValue : value)
55-
.catch(() => defaultValue); // Ignore exceptions in getting the executable.
56+
try {
57+
const output = await this.processService.exec(pythonPath, ['-c', 'import sys;print(sys.executable)'], {});
58+
const executablePath = output.stdout.trim();
59+
if (executablePath.length > 0 && await this.fs.fileExistsAsync(executablePath)) {
60+
return executablePath;
61+
}
62+
return defaultValue;
63+
} catch {
64+
return defaultValue; // Ignore exceptions in getting the executable.
65+
}
5666
}
5767
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { expect } from 'chai';
7+
import * as TypeMoq from 'typemoq';
8+
import { IFileSystem } from '../../client/common/platform/types';
9+
import { IProcessService } from '../../client/common/process/types';
10+
import { IConfigurationService, IPersistentState, IPersistentStateFactory, IPythonSettings } from '../../client/common/types';
11+
import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts';
12+
import { CurrentPathService } from '../../client/interpreter/locators/services/currentPathService';
13+
import { IVirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs/types';
14+
import { IServiceContainer } from '../../client/ioc/types';
15+
16+
// tslint:disable-next-line:max-func-body-length
17+
suite('Interpreters CurrentPath Service', () => {
18+
let processService: TypeMoq.IMock<IProcessService>;
19+
let fileSystem: TypeMoq.IMock<IFileSystem>;
20+
let serviceContainer: TypeMoq.IMock<IServiceContainer>;
21+
let virtualEnvironmentManager: TypeMoq.IMock<IVirtualEnvironmentManager>;
22+
let interpreterVersionService: TypeMoq.IMock<IInterpreterVersionService>;
23+
let pythonSettings: TypeMoq.IMock<IPythonSettings>;
24+
let currentPathService: CurrentPathService;
25+
let persistentState: TypeMoq.IMock<IPersistentState<PythonInterpreter[]>>;
26+
setup(async () => {
27+
processService = TypeMoq.Mock.ofType<IProcessService>();
28+
virtualEnvironmentManager = TypeMoq.Mock.ofType<IVirtualEnvironmentManager>();
29+
interpreterVersionService = TypeMoq.Mock.ofType<IInterpreterVersionService>();
30+
const configurationService = TypeMoq.Mock.ofType<IConfigurationService>();
31+
pythonSettings = TypeMoq.Mock.ofType<IPythonSettings>();
32+
configurationService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object);
33+
const persistentStateFactory = TypeMoq.Mock.ofType<IPersistentStateFactory>();
34+
persistentState = TypeMoq.Mock.ofType<IPersistentState<PythonInterpreter[]>>();
35+
// tslint:disable-next-line:no-any
36+
persistentState.setup(p => p.value).returns(() => undefined as any);
37+
persistentState.setup(p => p.updateValue(TypeMoq.It.isAny())).returns(() => Promise.resolve());
38+
fileSystem = TypeMoq.Mock.ofType<IFileSystem>();
39+
persistentStateFactory.setup(p => p.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => persistentState.object);
40+
41+
serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
42+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessService), TypeMoq.It.isAny())).returns(() => processService.object);
43+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IVirtualEnvironmentManager), TypeMoq.It.isAny())).returns(() => virtualEnvironmentManager.object);
44+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterVersionService), TypeMoq.It.isAny())).returns(() => interpreterVersionService.object);
45+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem), TypeMoq.It.isAny())).returns(() => fileSystem.object);
46+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory), TypeMoq.It.isAny())).returns(() => persistentStateFactory.object);
47+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configurationService.object);
48+
49+
currentPathService = new CurrentPathService(virtualEnvironmentManager.object, interpreterVersionService.object, processService.object, serviceContainer.object);
50+
});
51+
52+
test('Interpreters that do not exist on the file system are not excluded from the list', async () => {
53+
// Specific test for 1305
54+
const version = 'mockVersion';
55+
const envName = 'mockEnvName';
56+
interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(version));
57+
virtualEnvironmentManager.setup(v => v.getEnvironmentName(TypeMoq.It.isAny())).returns(() => Promise.resolve(envName));
58+
59+
const execArgs = ['-c', 'import sys;print(sys.executable)'];
60+
pythonSettings.setup(p => p.pythonPath).returns(() => 'root:Python');
61+
processService.setup(p => p.exec(TypeMoq.It.isValue('root:Python'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: 'c:/root:python' })).verifiable(TypeMoq.Times.once());
62+
processService.setup(p => p.exec(TypeMoq.It.isValue('python'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: 'c:/python1' })).verifiable(TypeMoq.Times.once());
63+
processService.setup(p => p.exec(TypeMoq.It.isValue('python2'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: 'c:/python2' })).verifiable(TypeMoq.Times.once());
64+
processService.setup(p => p.exec(TypeMoq.It.isValue('python3'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: 'c:/python3' })).verifiable(TypeMoq.Times.once());
65+
66+
fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue('c:/root:python'))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once());
67+
fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue('c:/python1'))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once());
68+
fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue('c:/python2'))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once());
69+
fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue('c:/python3'))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once());
70+
71+
const interpreters = await currentPathService.getInterpreters();
72+
processService.verifyAll();
73+
fileSystem.verifyAll();
74+
expect(interpreters).to.be.of.length(2);
75+
expect(interpreters).to.deep.include({ displayName: `${version} (${envName})`, path: 'c:/root:python', type: InterpreterType.VirtualEnv });
76+
expect(interpreters).to.deep.include({ displayName: `${version} (${envName})`, path: 'c:/python3', type: InterpreterType.VirtualEnv });
77+
});
78+
});

0 commit comments

Comments
 (0)