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

Skip to content

Commit 9bcce5e

Browse files
authored
Point release with fix for Issue 10250 (microsoft#10254)
* Fix to return env variables of interpreter that is not current interpreter (microsoft#10251) For #10250 * Bug fix * Update change log * Fix date * Oops * Fix linter
1 parent 4dc2d43 commit 9bcce5e

File tree

5 files changed

+173
-30
lines changed

5 files changed

+173
-30
lines changed

CHANGELOG.md

+60
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,65 @@
11
# Changelog
22

3+
## 2020.2.3 (21 February 2020)
4+
5+
### Fixes
6+
7+
1. Ensure to correctly return env variables of the activated interpreter, when dealing with non-workspace interpreters.
8+
([#10250](https://github.com/Microsoft/vscode-python/issues/10250))
9+
10+
### Thanks
11+
12+
Thanks to the following projects which we fully rely on to provide some of
13+
our features:
14+
15+
- [isort](https://pypi.org/project/isort/)
16+
- [jedi](https://pypi.org/project/jedi/)
17+
and [parso](https://pypi.org/project/parso/)
18+
- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server)
19+
- [ptvsd](https://pypi.org/project/ptvsd/)
20+
- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed)
21+
- [rope](https://pypi.org/project/rope/) (user-installed)
22+
23+
Also thanks to the various projects we provide integrations with which help
24+
make this extension useful:
25+
26+
- Debugging support:
27+
[Django](https://pypi.org/project/Django/),
28+
[Flask](https://pypi.org/project/Flask/),
29+
[gevent](https://pypi.org/project/gevent/),
30+
[Jinja](https://pypi.org/project/Jinja/),
31+
[Pyramid](https://pypi.org/project/pyramid/),
32+
[PySpark](https://pypi.org/project/pyspark/),
33+
[Scrapy](https://pypi.org/project/Scrapy/),
34+
[Watson](https://pypi.org/project/Watson/)
35+
- Formatting:
36+
[autopep8](https://pypi.org/project/autopep8/),
37+
[black](https://pypi.org/project/black/),
38+
[yapf](https://pypi.org/project/yapf/)
39+
- Interpreter support:
40+
[conda](https://conda.io/),
41+
[direnv](https://direnv.net/),
42+
[pipenv](https://pypi.org/project/pipenv/),
43+
[pyenv](https://github.com/pyenv/pyenv),
44+
[venv](https://docs.python.org/3/library/venv.html#module-venv),
45+
[virtualenv](https://pypi.org/project/virtualenv/)
46+
- Linting:
47+
[bandit](https://pypi.org/project/bandit/),
48+
[flake8](https://pypi.org/project/flake8/),
49+
[mypy](https://pypi.org/project/mypy/),
50+
[prospector](https://pypi.org/project/prospector/),
51+
[pylint](https://pypi.org/project/pylint/),
52+
[pydocstyle](https://pypi.org/project/pydocstyle/),
53+
[pylama](https://pypi.org/project/pylama/)
54+
- Testing:
55+
[nose](https://pypi.org/project/nose/),
56+
[pytest](https://pypi.org/project/pytest/),
57+
[unittest](https://docs.python.org/3/library/unittest.html#module-unittest)
58+
59+
And finally thanks to the [Python](https://www.python.org/) development team and
60+
community for creating a fantastic programming language and community to be a
61+
part of!
62+
363
## 2020.2.2 (19 February 2020)
464

565
### Fixes

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "python",
33
"displayName": "Python",
44
"description": "Linting, Debugging (multi-threaded, remote), Intellisense, Jupyter Notebooks, code formatting, refactoring, unit tests, snippets, and more.",
5-
"version": "2020.2.2",
5+
"version": "2020.2.3",
66
"languageServerVersion": "0.5.30",
77
"publisher": "ms-python",
88
"author": {

src/client/common/logger.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ function trace(message: string, options: LogOptions = LogOptions.None, logLevel?
285285
// tslint:disable-next-line:no-any
286286
function writeToLog(elapsedTime: number, returnValue?: any, ex?: Error) {
287287
const messagesToLog = [message];
288-
messagesToLog.push(`Class name = ${className}, completed in ${elapsedTime}ms`);
288+
messagesToLog.push(`Class name = ${className}, completed in ${elapsedTime}ms, has a ${returnValue ? 'truthy' : 'falsy'} return value`);
289289
if ((options && LogOptions.Arguments) === LogOptions.Arguments) {
290290
messagesToLog.push(argsToLogString(args));
291291
}

src/client/interpreter/activation/service.ts

+29-7
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@ import '../../common/extensions';
66
import { inject, injectable } from 'inversify';
77
import * as path from 'path';
88

9+
import { IWorkspaceService } from '../../common/application/types';
910
import { PYTHON_WARNINGS } from '../../common/constants';
1011
import { LogOptions, traceDecorators, traceError, traceVerbose } from '../../common/logger';
1112
import { IPlatformService } from '../../common/platform/types';
1213
import { IProcessServiceFactory } from '../../common/process/types';
1314
import { ITerminalHelper, TerminalShellType } from '../../common/terminal/types';
1415
import { ICurrentProcess, IDisposable, Resource } from '../../common/types';
15-
import { cacheResourceSpecificInterpreterData, clearCachedResourceSpecificIngterpreterData } from '../../common/utils/decorators';
16+
import { InMemoryCache } from '../../common/utils/cacheUtils';
1617
import { OSType } from '../../common/utils/platform';
1718
import { IEnvironmentVariablesProvider } from '../../common/variables/types';
1819
import { EXTENSION_ROOT_DIR } from '../../constants';
1920
import { captureTelemetry, sendTelemetryEvent } from '../../telemetry';
2021
import { EventName } from '../../telemetry/constants';
21-
import { PythonInterpreter } from '../contracts';
22+
import { IInterpreterService, PythonInterpreter } from '../contracts';
2223
import { IEnvironmentActivationService } from './types';
2324

2425
const getEnvironmentPrefix = 'e8b39361-0157-4923-80e1-22d70d46dee6';
@@ -36,23 +37,46 @@ const defaultShells = {
3637
@injectable()
3738
export class EnvironmentActivationService implements IEnvironmentActivationService, IDisposable {
3839
private readonly disposables: IDisposable[] = [];
40+
private readonly activatedEnvVariablesCache = new Map<string, InMemoryCache<NodeJS.ProcessEnv | undefined>>();
3941
constructor(
4042
@inject(ITerminalHelper) private readonly helper: ITerminalHelper,
4143
@inject(IPlatformService) private readonly platform: IPlatformService,
4244
@inject(IProcessServiceFactory) private processServiceFactory: IProcessServiceFactory,
4345
@inject(ICurrentProcess) private currentProcess: ICurrentProcess,
46+
@inject(IWorkspaceService) private workspace: IWorkspaceService,
47+
@inject(IInterpreterService) private interpreterService: IInterpreterService,
4448
@inject(IEnvironmentVariablesProvider) private readonly envVarsService: IEnvironmentVariablesProvider
4549
) {
46-
this.envVarsService.onDidEnvironmentVariablesChange(this.onDidEnvironmentVariablesChange, this, this.disposables);
50+
this.envVarsService.onDidEnvironmentVariablesChange(() => this.activatedEnvVariablesCache.clear(), this, this.disposables);
51+
52+
this.interpreterService.onDidChangeInterpreter(() => this.activatedEnvVariablesCache.clear(), this, this.disposables);
4753
}
4854

4955
public dispose(): void {
5056
this.disposables.forEach(d => d.dispose());
5157
}
5258
@traceDecorators.verbose('getActivatedEnvironmentVariables', LogOptions.Arguments)
5359
@captureTelemetry(EventName.PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES, { failed: false }, true)
54-
@cacheResourceSpecificInterpreterData('ActivatedEnvironmentVariables', cacheDuration)
5560
public async getActivatedEnvironmentVariables(resource: Resource, interpreter?: PythonInterpreter, allowExceptions?: boolean): Promise<NodeJS.ProcessEnv | undefined> {
61+
// Cache key = resource + interpreter.
62+
const workspaceKey = this.workspace.getWorkspaceFolderIdentifier(resource);
63+
const interpreterPath = this.platform.isWindows ? interpreter?.path.toLowerCase() : interpreter?.path;
64+
const cacheKey = `${workspaceKey}_${interpreterPath}`;
65+
66+
if (this.activatedEnvVariablesCache.get(cacheKey)?.hasData) {
67+
return this.activatedEnvVariablesCache.get(cacheKey)!.data;
68+
}
69+
70+
// Cache only if successful, else keep trying & failing if necessary.
71+
const cache = new InMemoryCache<NodeJS.ProcessEnv | undefined>(cacheDuration, '');
72+
return this.getActivatedEnvironmentVariablesImpl(resource, interpreter, allowExceptions).then(vars => {
73+
cache.data = vars;
74+
this.activatedEnvVariablesCache.set(cacheKey, cache);
75+
return vars;
76+
});
77+
}
78+
79+
public async getActivatedEnvironmentVariablesImpl(resource: Resource, interpreter?: PythonInterpreter, allowExceptions?: boolean): Promise<NodeJS.ProcessEnv | undefined> {
5680
const shellInfo = defaultShells[this.platform.osType];
5781
if (!shellInfo) {
5882
return;
@@ -115,9 +139,7 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi
115139
}
116140
}
117141
}
118-
protected onDidEnvironmentVariablesChange(affectedResource: Resource) {
119-
clearCachedResourceSpecificIngterpreterData('ActivatedEnvironmentVariables', affectedResource);
120-
}
142+
121143
protected fixActivationCommands(commands: string[]): string[] {
122144
// Replace 'source ' with '. ' as that works in shell exec
123145
return commands.map(cmd => cmd.replace(/^source\s+/, '. '));

src/test/interpreters/activation/service.unit.test.ts

+82-21
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { EOL } from 'os';
77
import * as path from 'path';
88
import { SemVer } from 'semver';
99
import { anything, capture, instance, mock, verify, when } from 'ts-mockito';
10-
import * as typemoq from 'typemoq';
11-
import { EventEmitter, Uri, workspace as workspaceType, WorkspaceConfiguration } from 'vscode';
10+
import { EventEmitter, Uri } from 'vscode';
11+
import { IWorkspaceService } from '../../../client/common/application/types';
12+
import { WorkspaceService } from '../../../client/common/application/workspace';
1213
import { PlatformService } from '../../../client/common/platform/platformService';
1314
import { IPlatformService } from '../../../client/common/platform/types';
1415
import { CurrentProcess } from '../../../client/common/process/currentProcess';
@@ -18,15 +19,14 @@ import { IProcessService, IProcessServiceFactory } from '../../../client/common/
1819
import { TerminalHelper } from '../../../client/common/terminal/helper';
1920
import { ITerminalHelper } from '../../../client/common/terminal/types';
2021
import { ICurrentProcess } from '../../../client/common/types';
21-
import { clearCache } from '../../../client/common/utils/cacheUtils';
2222
import { getNamesAndValues } from '../../../client/common/utils/enum';
2323
import { Architecture, OSType } from '../../../client/common/utils/platform';
2424
import { EnvironmentVariablesProvider } from '../../../client/common/variables/environmentVariablesProvider';
2525
import { IEnvironmentVariablesProvider } from '../../../client/common/variables/types';
2626
import { EXTENSION_ROOT_DIR } from '../../../client/constants';
2727
import { EnvironmentActivationService } from '../../../client/interpreter/activation/service';
28-
import { InterpreterType, PythonInterpreter } from '../../../client/interpreter/contracts';
29-
import { mockedVSCodeNamespaces } from '../../vscode-mock';
28+
import { IInterpreterService, InterpreterType, PythonInterpreter } from '../../../client/interpreter/contracts';
29+
import { InterpreterService } from '../../../client/interpreter/interpreterService';
3030

3131
const getEnvironmentPrefix = 'e8b39361-0157-4923-80e1-22d70d46dee6';
3232
const defaultShells = {
@@ -45,8 +45,10 @@ suite('Interpreters Activation - Python Environment Variables', () => {
4545
let processService: IProcessService;
4646
let currentProcess: ICurrentProcess;
4747
let envVarsService: IEnvironmentVariablesProvider;
48-
let workspace: typemoq.IMock<typeof workspaceType>;
49-
48+
let workspace: IWorkspaceService;
49+
let interpreterService: IInterpreterService;
50+
let onDidChangeEnvVariables: EventEmitter<Uri | undefined>;
51+
let onDidChangeInterpreter: EventEmitter<void>;
5052
const pythonInterpreter: PythonInterpreter = {
5153
path: '/foo/bar/python.exe',
5254
version: new SemVer('3.6.6-final'),
@@ -63,21 +65,22 @@ suite('Interpreters Activation - Python Environment Variables', () => {
6365
processService = mock(ProcessService);
6466
currentProcess = mock(CurrentProcess);
6567
envVarsService = mock(EnvironmentVariablesProvider);
66-
workspace = mockedVSCodeNamespaces.workspace!;
67-
when(envVarsService.onDidEnvironmentVariablesChange).thenReturn(new EventEmitter<Uri | undefined>().event);
68-
service = new EnvironmentActivationService(instance(helper), instance(platform), instance(processServiceFactory), instance(currentProcess), instance(envVarsService));
69-
70-
const cfg = typemoq.Mock.ofType<WorkspaceConfiguration>();
71-
workspace.setup(w => w.getConfiguration(typemoq.It.isValue('python'), typemoq.It.isAny())).returns(() => cfg.object);
72-
workspace.setup(w => w.workspaceFolders).returns(() => []);
73-
cfg.setup(c => c.inspect(typemoq.It.isValue('pythonPath'))).returns(() => {
74-
return { globalValue: 'GlobalValuepython' } as any;
75-
});
76-
clearCache();
68+
interpreterService = mock(InterpreterService);
69+
workspace = mock(WorkspaceService);
70+
onDidChangeEnvVariables = new EventEmitter<Uri | undefined>();
71+
onDidChangeInterpreter = new EventEmitter<void>();
72+
when(envVarsService.onDidEnvironmentVariablesChange).thenReturn(onDidChangeEnvVariables.event);
73+
when(interpreterService.onDidChangeInterpreter).thenReturn(onDidChangeInterpreter.event);
74+
service = new EnvironmentActivationService(
75+
instance(helper),
76+
instance(platform),
77+
instance(processServiceFactory),
78+
instance(currentProcess),
79+
instance(workspace),
80+
instance(interpreterService),
81+
instance(envVarsService)
82+
);
7783
}
78-
teardown(() => {
79-
mockedVSCodeNamespaces.workspace!.reset();
80-
});
8184

8285
function title(resource?: Uri, interpreter?: PythonInterpreter) {
8386
return `${resource ? 'With a resource' : 'Without a resource'}${interpreter ? ' and an interpreter' : ''}`;
@@ -218,6 +221,64 @@ suite('Interpreters Activation - Python Environment Variables', () => {
218221
verify(envVarsService.getEnvironmentVariables(resource)).once();
219222
verify(processService.shellExec(anything(), anything())).once();
220223
});
224+
test('Cache Variables', async () => {
225+
const cmd = ['1', '2'];
226+
const varsFromEnv = { one: '11', two: '22', HELLO: 'xxx' };
227+
const stdout = `${getEnvironmentPrefix}${EOL}${JSON.stringify(varsFromEnv)}`;
228+
when(platform.osType).thenReturn(osType.value);
229+
when(helper.getEnvironmentActivationShellCommands(resource, anything(), interpreter)).thenResolve(cmd);
230+
when(processServiceFactory.create(resource)).thenResolve(instance(processService));
231+
when(envVarsService.getEnvironmentVariables(resource)).thenResolve({});
232+
when(processService.shellExec(anything(), anything())).thenResolve({ stdout: stdout });
233+
234+
const env = await service.getActivatedEnvironmentVariables(resource, interpreter);
235+
const env2 = await service.getActivatedEnvironmentVariables(resource, interpreter);
236+
const env3 = await service.getActivatedEnvironmentVariables(resource, interpreter);
237+
238+
expect(env).to.deep.equal(varsFromEnv);
239+
// All same objects.
240+
expect(env)
241+
.to.equal(env2)
242+
.to.equal(env3);
243+
244+
// All methods invoked only once.
245+
verify(helper.getEnvironmentActivationShellCommands(resource, anything(), interpreter)).once();
246+
verify(processServiceFactory.create(resource)).once();
247+
verify(envVarsService.getEnvironmentVariables(resource)).once();
248+
verify(processService.shellExec(anything(), anything())).once();
249+
});
250+
async function testClearingCache(bustCache: Function) {
251+
const cmd = ['1', '2'];
252+
const varsFromEnv = { one: '11', two: '22', HELLO: 'xxx' };
253+
const stdout = `${getEnvironmentPrefix}${EOL}${JSON.stringify(varsFromEnv)}`;
254+
when(platform.osType).thenReturn(osType.value);
255+
when(helper.getEnvironmentActivationShellCommands(resource, anything(), interpreter)).thenResolve(cmd);
256+
when(processServiceFactory.create(resource)).thenResolve(instance(processService));
257+
when(envVarsService.getEnvironmentVariables(resource)).thenResolve({});
258+
when(processService.shellExec(anything(), anything())).thenResolve({ stdout: stdout });
259+
260+
const env = await service.getActivatedEnvironmentVariables(resource, interpreter);
261+
bustCache();
262+
const env2 = await service.getActivatedEnvironmentVariables(resource, interpreter);
263+
264+
expect(env).to.deep.equal(varsFromEnv);
265+
// Objects are different (not same reference).
266+
expect(env).to.not.equal(env2);
267+
// However variables are the same.
268+
expect(env).to.deep.equal(env2);
269+
270+
// All methods invoked twice as cache was blown.
271+
verify(helper.getEnvironmentActivationShellCommands(resource, anything(), interpreter)).twice();
272+
verify(processServiceFactory.create(resource)).twice();
273+
verify(envVarsService.getEnvironmentVariables(resource)).twice();
274+
verify(processService.shellExec(anything(), anything())).twice();
275+
}
276+
test('Cache Variables get cleared when changing interpreter', async () => {
277+
await testClearingCache(onDidChangeInterpreter.fire.bind(onDidChangeInterpreter));
278+
});
279+
test('Cache Variables get cleared when changing env variables file', async () => {
280+
await testClearingCache(onDidChangeEnvVariables.fire.bind(onDidChangeEnvVariables));
281+
});
221282
});
222283
});
223284
});

0 commit comments

Comments
 (0)