Skip to content

Commit 64c8526

Browse files
Virtualenvwrapper locator (#13895)
* virtualenvwrapper locator * skip os-specific tests * Test getDefaultVirtualenvwrapperDir * Update src/client/pythonEnvironments/discovery/locators/services/virtualenvwrapperLocator.ts Co-authored-by: Karthik Nadig <[email protected]> * add pathExists check to WORKON_HOME dir * Change how the locator works + move util func out * Fix functional tests * utils unit tests * Update locator tests * Stub getUserHomeDir instead of getDefaultetc Co-authored-by: Karthik Nadig <[email protected]>
1 parent f687921 commit 64c8526

File tree

9 files changed

+218
-0
lines changed

9 files changed

+218
-0
lines changed

src/client/pythonEnvironments/common/environmentIdentifier.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { isCondaEnvironment } from '../discovery/locators/services/condaLocator'
55
import { isPipenvEnvironment } from '../discovery/locators/services/pipEnvHelper';
66
import { isVenvEnvironment } from '../discovery/locators/services/venvLocator';
77
import { isVirtualenvEnvironment } from '../discovery/locators/services/virtualenvLocator';
8+
import { isVirtualenvwrapperEnvironment } from '../discovery/locators/services/virtualenvwrapperLocator';
89
import { isWindowsStoreEnvironment } from '../discovery/locators/services/windowsStoreLocator';
910
import { EnvironmentType } from '../info';
1011

@@ -48,6 +49,10 @@ export async function identifyEnvironment(interpreterPath: string): Promise<Envi
4849
return EnvironmentType.Venv;
4950
}
5051

52+
if (await isVirtualenvwrapperEnvironment(interpreterPath)) {
53+
return EnvironmentType.VirtualEnvWrapper;
54+
}
55+
5156
if (await isVirtualenvEnvironment(interpreterPath)) {
5257
return EnvironmentType.VirtualEnv;
5358
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
import * as path from 'path';
4+
import { getOSType, getUserHomeDir, OSType } from '../../common/utils/platform';
5+
6+
export function getDefaultVirtualenvwrapperDir(): string {
7+
const homeDir = getUserHomeDir() || '';
8+
9+
// In Windows, the default path for WORKON_HOME is %USERPROFILE%\Envs.
10+
if (getOSType() === OSType.Windows) {
11+
return path.join(homeDir, 'Envs');
12+
}
13+
return path.join(homeDir, '.virtualenvs');
14+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import * as path from 'path';
5+
import {
6+
getEnvironmentVariable, getOSType, OSType,
7+
} from '../../../../common/utils/platform';
8+
import { pathExists } from '../../../common/externalDependencies';
9+
import { getDefaultVirtualenvwrapperDir } from '../../../common/virtualenvwrapperUtils';
10+
11+
/**
12+
* Checks if the given interpreter belongs to a virtualenvWrapper based environment.
13+
* @param {string} interpreterPath: Absolute path to the python interpreter.
14+
* @returns {boolean}: Returns true if the interpreter belongs to a virtualenvWrapper environment.
15+
*/
16+
export async function isVirtualenvwrapperEnvironment(interpreterPath:string): Promise<boolean> {
17+
// The WORKON_HOME variable contains the path to the root directory of all virtualenvwrapper environments.
18+
// If the interpreter path belongs to one of them then it is a virtualenvwrapper type of environment.
19+
const workonHomeDir = getEnvironmentVariable('WORKON_HOME') || getDefaultVirtualenvwrapperDir();
20+
const environmentName = path.basename(path.dirname(path.dirname(interpreterPath)));
21+
22+
let environmentDir = path.join(workonHomeDir, environmentName);
23+
24+
if (getOSType() === OSType.Windows) {
25+
environmentDir = environmentDir.toUpperCase();
26+
}
27+
28+
return await pathExists(environmentDir) && interpreterPath.startsWith(`${environmentDir}${path.sep}`);
29+
}

src/test/pythonEnvironments/common/environmentIdentifier.unit.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as platformApis from '../../../client/common/utils/platform';
88
import { identifyEnvironment } from '../../../client/pythonEnvironments/common/environmentIdentifier';
99
import * as externalDependencies from '../../../client/pythonEnvironments/common/externalDependencies';
1010
import { EnvironmentType } from '../../../client/pythonEnvironments/info';
11+
import { getOSType as getOSTypeForTest, OSType } from '../../common';
1112
import { TEST_LAYOUT_ROOT } from './commonTestConstants';
1213

1314
suite('Environment Identifier', () => {
@@ -145,6 +146,69 @@ suite('Environment Identifier', () => {
145146
});
146147
});
147148

149+
suite('Virtualenvwrapper', () => {
150+
let getEnvVarStub: sinon.SinonStub;
151+
let getOsTypeStub: sinon.SinonStub;
152+
let getUserHomeDirStub: sinon.SinonStub;
153+
154+
suiteSetup(() => {
155+
getEnvVarStub = sinon.stub(platformApis, 'getEnvironmentVariable');
156+
getOsTypeStub = sinon.stub(platformApis, 'getOSType');
157+
getUserHomeDirStub = sinon.stub(platformApis, 'getUserHomeDir');
158+
159+
getUserHomeDirStub.returns(path.join(TEST_LAYOUT_ROOT, 'virtualenvwrapper1'));
160+
});
161+
162+
suiteTeardown(() => {
163+
getEnvVarStub.restore();
164+
getOsTypeStub.restore();
165+
getUserHomeDirStub.restore();
166+
});
167+
168+
test('WORKON_HOME is set to its default value ~/.virtualenvs on non-Windows', async function () {
169+
if (getOSTypeForTest() === OSType.Windows) {
170+
// tslint:disable-next-line: no-invalid-this
171+
return this.skip();
172+
}
173+
174+
const interpreterPath = path.join(TEST_LAYOUT_ROOT, 'virtualenvwrapper1', '.virtualenvs', 'myenv', 'bin', 'python');
175+
176+
getEnvVarStub.withArgs('WORKON_HOME').returns(undefined);
177+
178+
const envType = await identifyEnvironment(interpreterPath);
179+
assert.deepStrictEqual(envType, EnvironmentType.VirtualEnvWrapper);
180+
181+
return undefined;
182+
});
183+
184+
test('WORKON_HOME is set to its default value %USERPROFILE%\\Envs on Windows', async function () {
185+
if (getOSTypeForTest() !== OSType.Windows) {
186+
// tslint:disable-next-line: no-invalid-this
187+
return this.skip();
188+
}
189+
190+
const interpreterPath = path.join(TEST_LAYOUT_ROOT, 'virtualenvwrapper1', 'Envs', 'myenv', 'Scripts', 'python');
191+
192+
getEnvVarStub.withArgs('WORKON_HOME').returns(undefined);
193+
getOsTypeStub.returns(platformApis.OSType.Windows);
194+
195+
const envType = await identifyEnvironment(interpreterPath);
196+
assert.deepStrictEqual(envType, EnvironmentType.VirtualEnvWrapper);
197+
198+
return undefined;
199+
});
200+
201+
test('WORKON_HOME is set to a custom value', async () => {
202+
const workonHomeDir = path.join(TEST_LAYOUT_ROOT, 'virtualenvwrapper2');
203+
const interpreterPath = path.join(workonHomeDir, 'myenv', 'bin', 'python');
204+
205+
getEnvVarStub.withArgs('WORKON_HOME').returns(workonHomeDir);
206+
207+
const envType = await identifyEnvironment(interpreterPath);
208+
assert.deepStrictEqual(envType, EnvironmentType.VirtualEnvWrapper);
209+
});
210+
});
211+
148212
suite('Virtualenv', () => {
149213
const activateFiles = [
150214
{ folder: 'virtualenv1', file: 'activate' },

src/test/pythonEnvironments/common/envlayouts/virtualenvwrapper1/.virtualenvs/myenv/bin/python

Whitespace-only changes.

src/test/pythonEnvironments/common/envlayouts/virtualenvwrapper1/Envs/myenv/Scripts/python.exe

Whitespace-only changes.

src/test/pythonEnvironments/common/envlayouts/virtualenvwrapper2/myenv/bin/python

Whitespace-only changes.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import * as assert from 'assert';
5+
import * as path from 'path';
6+
import * as sinon from 'sinon';
7+
import * as platformUtils from '../../../client/common/utils/platform';
8+
import { getDefaultVirtualenvwrapperDir } from '../../../client/pythonEnvironments/common/virtualenvwrapperUtils';
9+
10+
suite('Virtualenvwrapper Utils tests', () => {
11+
const homeDir = path.join('path', 'to', 'home');
12+
13+
let getOsTypeStub: sinon.SinonStub;
14+
let getHomeDirStub: sinon.SinonStub;
15+
16+
setup(() => {
17+
getOsTypeStub = sinon.stub(platformUtils, 'getOSType');
18+
getHomeDirStub = sinon.stub(platformUtils, 'getUserHomeDir');
19+
20+
getHomeDirStub.returns(homeDir);
21+
});
22+
23+
teardown(() => {
24+
getOsTypeStub.restore();
25+
getHomeDirStub.restore();
26+
});
27+
28+
test('Default virtualenvwrapper directory on non-Windows should be ~/.virtualenvs', () => {
29+
getOsTypeStub.returns(platformUtils.OSType.Linux);
30+
31+
const directory = getDefaultVirtualenvwrapperDir();
32+
33+
assert.deepStrictEqual(directory, path.join(homeDir, '.virtualenvs'));
34+
});
35+
36+
test('Default virtualenvwrapper directory on Windows should be %USERPROFILE%\\Envs', () => {
37+
getOsTypeStub.returns(platformUtils.OSType.Windows);
38+
39+
const directory = getDefaultVirtualenvwrapperDir();
40+
41+
assert.deepStrictEqual(directory, path.join(homeDir, 'Envs'));
42+
});
43+
});
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import * as assert from 'assert';
5+
import * as path from 'path';
6+
import * as sinon from 'sinon';
7+
import * as platformUtils from '../../../../client/common/utils/platform';
8+
import * as fileUtils from '../../../../client/pythonEnvironments/common/externalDependencies';
9+
import * as virtualenvwrapperUtils from '../../../../client/pythonEnvironments/common/virtualenvwrapperUtils';
10+
import { isVirtualenvwrapperEnvironment } from '../../../../client/pythonEnvironments/discovery/locators/services/virtualenvwrapperLocator';
11+
12+
suite('Virtualenvwrapper Locator Tests', () => {
13+
const envDirectory = 'myenv';
14+
const homeDir = path.join('path', 'to', 'home');
15+
16+
let getEnvVariableStub: sinon.SinonStub;
17+
let pathExistsStub:sinon.SinonStub;
18+
let getDefaultDirStub:sinon.SinonStub;
19+
20+
setup(() => {
21+
getEnvVariableStub = sinon.stub(platformUtils, 'getEnvironmentVariable');
22+
pathExistsStub = sinon.stub(fileUtils, 'pathExists');
23+
getDefaultDirStub = sinon.stub(virtualenvwrapperUtils, 'getDefaultVirtualenvwrapperDir');
24+
25+
pathExistsStub.withArgs(path.join(homeDir, envDirectory)).resolves(true);
26+
pathExistsStub.resolves(false);
27+
});
28+
29+
teardown(() => {
30+
getEnvVariableStub.restore();
31+
pathExistsStub.restore();
32+
getDefaultDirStub.restore();
33+
});
34+
35+
test('WORKON_HOME is not set, and the interpreter is is in a subfolder', async () => {
36+
const interpreter = path.join(homeDir, envDirectory, 'bin', 'python');
37+
38+
getEnvVariableStub.withArgs('WORKON_HOME').returns(undefined);
39+
getDefaultDirStub.returns(homeDir);
40+
41+
assert.ok(await isVirtualenvwrapperEnvironment(interpreter));
42+
});
43+
44+
test('WORKON_HOME is set to a custom value, and the interpreter is is in a subfolder', async () => {
45+
const workonHomeDirectory = path.join('path', 'to', 'workonHome');
46+
const interpreter = path.join(workonHomeDirectory, envDirectory, 'bin', 'python');
47+
48+
getEnvVariableStub.withArgs('WORKON_HOME').returns(workonHomeDirectory);
49+
pathExistsStub.withArgs(path.join(workonHomeDirectory, envDirectory)).resolves(true);
50+
51+
assert.ok(await isVirtualenvwrapperEnvironment(interpreter));
52+
});
53+
54+
test('The interpreter is not in a subfolder of WORKON_HOME', async () => {
55+
const workonHomeDirectory = path.join('path', 'to', 'workonHome');
56+
const interpreter = path.join('some', 'path', envDirectory, 'bin', 'python');
57+
58+
getEnvVariableStub.withArgs('WORKON_HOME').returns(workonHomeDirectory);
59+
pathExistsStub.withArgs(path.join(workonHomeDirectory, envDirectory)).resolves(false);
60+
61+
assert.deepStrictEqual(await isVirtualenvwrapperEnvironment(interpreter), false);
62+
});
63+
});

0 commit comments

Comments
 (0)