Skip to content
5 changes: 5 additions & 0 deletions src/client/pythonEnvironments/common/environmentIdentifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isCondaEnvironment } from '../discovery/locators/services/condaLocator'
import { isPipenvEnvironment } from '../discovery/locators/services/pipEnvHelper';
import { isVenvEnvironment } from '../discovery/locators/services/venvLocator';
import { isVirtualenvEnvironment } from '../discovery/locators/services/virtualenvLocator';
import { isVirtualenvwrapperEnvironment } from '../discovery/locators/services/virtualenvwrapperLocator';
import { isWindowsStoreEnvironment } from '../discovery/locators/services/windowsStoreLocator';
import { EnvironmentType } from '../info';

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

if (await isVirtualenvwrapperEnvironment(interpreterPath)) {
return EnvironmentType.VirtualEnvWrapper;
}

if (await isVirtualenvEnvironment(interpreterPath)) {
return EnvironmentType.VirtualEnv;
}
Expand Down
14 changes: 14 additions & 0 deletions src/client/pythonEnvironments/common/virtualenvwrapperUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as path from 'path';
import { getOSType, getUserHomeDir, OSType } from '../../common/utils/platform';

export function getDefaultVirtualenvwrapperDir(): string {
const homeDir = getUserHomeDir() || '';

// In Windows, the default path for WORKON_HOME is %USERPROFILE%\Envs.
if (getOSType() === OSType.Windows) {
return path.join(homeDir, 'Envs');
}
return path.join(homeDir, '.virtualenvs');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import * as path from 'path';
import {
getEnvironmentVariable, getOSType, OSType,
} from '../../../../common/utils/platform';
import { pathExists } from '../../../common/externalDependencies';
import { getDefaultVirtualenvwrapperDir } from '../../../common/virtualenvwrapperUtils';

/**
* Checks if the given interpreter belongs to a virtualenvWrapper based environment.
* @param {string} interpreterPath: Absolute path to the python interpreter.
* @returns {boolean}: Returns true if the interpreter belongs to a virtualenvWrapper environment.
*/
export async function isVirtualenvwrapperEnvironment(interpreterPath:string): Promise<boolean> {
// The WORKON_HOME variable contains the path to the root directory of all virtualenvwrapper environments.
// If the interpreter path belongs to one of them then it is a virtualenvwrapper type of environment.
const workonHomeDir = getEnvironmentVariable('WORKON_HOME') || getDefaultVirtualenvwrapperDir();
const environmentName = path.basename(path.dirname(path.dirname(interpreterPath)));

let environmentDir = path.join(workonHomeDir, environmentName);

if (getOSType() === OSType.Windows) {
environmentDir = environmentDir.toUpperCase();
}

return await pathExists(environmentDir) && interpreterPath.startsWith(`${environmentDir}${path.sep}`);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as platformApis from '../../../client/common/utils/platform';
import { identifyEnvironment } from '../../../client/pythonEnvironments/common/environmentIdentifier';
import * as externalDependencies from '../../../client/pythonEnvironments/common/externalDependencies';
import { EnvironmentType } from '../../../client/pythonEnvironments/info';
import { getOSType as getOSTypeForTest, OSType } from '../../common';
import { TEST_LAYOUT_ROOT } from './commonTestConstants';

suite('Environment Identifier', () => {
Expand Down Expand Up @@ -145,6 +146,69 @@ suite('Environment Identifier', () => {
});
});

suite('Virtualenvwrapper', () => {
let getEnvVarStub: sinon.SinonStub;
let getOsTypeStub: sinon.SinonStub;
let getUserHomeDirStub: sinon.SinonStub;

suiteSetup(() => {
getEnvVarStub = sinon.stub(platformApis, 'getEnvironmentVariable');
getOsTypeStub = sinon.stub(platformApis, 'getOSType');
getUserHomeDirStub = sinon.stub(platformApis, 'getUserHomeDir');

getUserHomeDirStub.returns(path.join(TEST_LAYOUT_ROOT, 'virtualenvwrapper1'));
});

suiteTeardown(() => {
getEnvVarStub.restore();
getOsTypeStub.restore();
getUserHomeDirStub.restore();
});

test('WORKON_HOME is set to its default value ~/.virtualenvs on non-Windows', async function () {
if (getOSTypeForTest() === OSType.Windows) {
// tslint:disable-next-line: no-invalid-this
return this.skip();
}

const interpreterPath = path.join(TEST_LAYOUT_ROOT, 'virtualenvwrapper1', '.virtualenvs', 'myenv', 'bin', 'python');

getEnvVarStub.withArgs('WORKON_HOME').returns(undefined);

const envType = await identifyEnvironment(interpreterPath);
assert.deepStrictEqual(envType, EnvironmentType.VirtualEnvWrapper);
Comment thread
karrtikr marked this conversation as resolved.

return undefined;
Comment thread
karrtikr marked this conversation as resolved.
});

test('WORKON_HOME is set to its default value %USERPROFILE%\\Envs on Windows', async function () {
if (getOSTypeForTest() !== OSType.Windows) {
// tslint:disable-next-line: no-invalid-this
return this.skip();
}

const interpreterPath = path.join(TEST_LAYOUT_ROOT, 'virtualenvwrapper1', 'Envs', 'myenv', 'Scripts', 'python');

getEnvVarStub.withArgs('WORKON_HOME').returns(undefined);
getOsTypeStub.returns(platformApis.OSType.Windows);

const envType = await identifyEnvironment(interpreterPath);
assert.deepStrictEqual(envType, EnvironmentType.VirtualEnvWrapper);

return undefined;
});

test('WORKON_HOME is set to a custom value', async () => {
const workonHomeDir = path.join(TEST_LAYOUT_ROOT, 'virtualenvwrapper2');
const interpreterPath = path.join(workonHomeDir, 'myenv', 'bin', 'python');

getEnvVarStub.withArgs('WORKON_HOME').returns(workonHomeDir);

const envType = await identifyEnvironment(interpreterPath);
assert.deepStrictEqual(envType, EnvironmentType.VirtualEnvWrapper);
});
});

suite('Virtualenv', () => {
const activateFiles = [
{ folder: 'virtualenv1', file: 'activate' },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import * as assert from 'assert';
import * as path from 'path';
import * as sinon from 'sinon';
import * as platformUtils from '../../../client/common/utils/platform';
import { getDefaultVirtualenvwrapperDir } from '../../../client/pythonEnvironments/common/virtualenvwrapperUtils';

suite('Virtualenvwrapper Utils tests', () => {
const homeDir = path.join('path', 'to', 'home');

let getOsTypeStub: sinon.SinonStub;
let getHomeDirStub: sinon.SinonStub;

setup(() => {
getOsTypeStub = sinon.stub(platformUtils, 'getOSType');
getHomeDirStub = sinon.stub(platformUtils, 'getUserHomeDir');

getHomeDirStub.returns(homeDir);
});

teardown(() => {
getOsTypeStub.restore();
getHomeDirStub.restore();
});

test('Default virtualenvwrapper directory on non-Windows should be ~/.virtualenvs', () => {
getOsTypeStub.returns(platformUtils.OSType.Linux);

const directory = getDefaultVirtualenvwrapperDir();

assert.deepStrictEqual(directory, path.join(homeDir, '.virtualenvs'));
});

test('Default virtualenvwrapper directory on Windows should be %USERPROFILE%\\Envs', () => {
getOsTypeStub.returns(platformUtils.OSType.Windows);

const directory = getDefaultVirtualenvwrapperDir();

assert.deepStrictEqual(directory, path.join(homeDir, 'Envs'));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import * as assert from 'assert';
import * as path from 'path';
import * as sinon from 'sinon';
import * as platformUtils from '../../../../client/common/utils/platform';
import * as fileUtils from '../../../../client/pythonEnvironments/common/externalDependencies';
import * as virtualenvwrapperUtils from '../../../../client/pythonEnvironments/common/virtualenvwrapperUtils';
import { isVirtualenvwrapperEnvironment } from '../../../../client/pythonEnvironments/discovery/locators/services/virtualenvwrapperLocator';

suite('Virtualenvwrapper Locator Tests', () => {
const envDirectory = 'myenv';
const homeDir = path.join('path', 'to', 'home');

let getEnvVariableStub: sinon.SinonStub;
let pathExistsStub:sinon.SinonStub;
let getDefaultDirStub:sinon.SinonStub;

setup(() => {
getEnvVariableStub = sinon.stub(platformUtils, 'getEnvironmentVariable');
pathExistsStub = sinon.stub(fileUtils, 'pathExists');
getDefaultDirStub = sinon.stub(virtualenvwrapperUtils, 'getDefaultVirtualenvwrapperDir');

pathExistsStub.withArgs(path.join(homeDir, envDirectory)).resolves(true);
pathExistsStub.resolves(false);
});

teardown(() => {
getEnvVariableStub.restore();
pathExistsStub.restore();
getDefaultDirStub.restore();
});

test('WORKON_HOME is not set, and the interpreter is is in a subfolder', async () => {
const interpreter = path.join(homeDir, envDirectory, 'bin', 'python');

getEnvVariableStub.withArgs('WORKON_HOME').returns(undefined);
getDefaultDirStub.returns(homeDir);

assert.ok(await isVirtualenvwrapperEnvironment(interpreter));
});

test('WORKON_HOME is set to a custom value, and the interpreter is is in a subfolder', async () => {
const workonHomeDirectory = path.join('path', 'to', 'workonHome');
const interpreter = path.join(workonHomeDirectory, envDirectory, 'bin', 'python');

getEnvVariableStub.withArgs('WORKON_HOME').returns(workonHomeDirectory);
pathExistsStub.withArgs(path.join(workonHomeDirectory, envDirectory)).resolves(true);

assert.ok(await isVirtualenvwrapperEnvironment(interpreter));
});

test('The interpreter is not in a subfolder of WORKON_HOME', async () => {
const workonHomeDirectory = path.join('path', 'to', 'workonHome');
const interpreter = path.join('some', 'path', envDirectory, 'bin', 'python');

getEnvVariableStub.withArgs('WORKON_HOME').returns(workonHomeDirectory);
pathExistsStub.withArgs(path.join(workonHomeDirectory, envDirectory)).resolves(false);

assert.deepStrictEqual(await isVirtualenvwrapperEnvironment(interpreter), false);
});
});